/src/postgres/src/backend/utils/adt/ri_triggers.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * ri_triggers.c |
4 | | * |
5 | | * Generic trigger procedures for referential integrity constraint |
6 | | * checks. |
7 | | * |
8 | | * Note about memory management: the private hashtables kept here live |
9 | | * across query and transaction boundaries, in fact they live as long as |
10 | | * the backend does. This works because the hashtable structures |
11 | | * themselves are allocated by dynahash.c in its permanent DynaHashCxt, |
12 | | * and the SPI plans they point to are saved using SPI_keepplan(). |
13 | | * There is not currently any provision for throwing away a no-longer-needed |
14 | | * plan --- consider improving this someday. |
15 | | * |
16 | | * |
17 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
18 | | * |
19 | | * src/backend/utils/adt/ri_triggers.c |
20 | | * |
21 | | *------------------------------------------------------------------------- |
22 | | */ |
23 | | |
24 | | #include "postgres.h" |
25 | | |
26 | | #include "access/htup_details.h" |
27 | | #include "access/sysattr.h" |
28 | | #include "access/table.h" |
29 | | #include "access/tableam.h" |
30 | | #include "access/xact.h" |
31 | | #include "catalog/pg_collation.h" |
32 | | #include "catalog/pg_constraint.h" |
33 | | #include "commands/trigger.h" |
34 | | #include "executor/executor.h" |
35 | | #include "executor/spi.h" |
36 | | #include "lib/ilist.h" |
37 | | #include "miscadmin.h" |
38 | | #include "parser/parse_coerce.h" |
39 | | #include "parser/parse_relation.h" |
40 | | #include "utils/acl.h" |
41 | | #include "utils/builtins.h" |
42 | | #include "utils/datum.h" |
43 | | #include "utils/fmgroids.h" |
44 | | #include "utils/guc.h" |
45 | | #include "utils/inval.h" |
46 | | #include "utils/lsyscache.h" |
47 | | #include "utils/memutils.h" |
48 | | #include "utils/rel.h" |
49 | | #include "utils/rls.h" |
50 | | #include "utils/ruleutils.h" |
51 | | #include "utils/snapmgr.h" |
52 | | #include "utils/syscache.h" |
53 | | |
54 | | /* |
55 | | * Local definitions |
56 | | */ |
57 | | |
58 | | #define RI_MAX_NUMKEYS INDEX_MAX_KEYS |
59 | | |
60 | 0 | #define RI_INIT_CONSTRAINTHASHSIZE 64 |
61 | 0 | #define RI_INIT_QUERYHASHSIZE (RI_INIT_CONSTRAINTHASHSIZE * 4) |
62 | | |
63 | 0 | #define RI_KEYS_ALL_NULL 0 |
64 | 0 | #define RI_KEYS_SOME_NULL 1 |
65 | 0 | #define RI_KEYS_NONE_NULL 2 |
66 | | |
67 | | /* RI query type codes */ |
68 | | /* these queries are executed against the PK (referenced) table: */ |
69 | 0 | #define RI_PLAN_CHECK_LOOKUPPK 1 |
70 | 0 | #define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2 |
71 | 0 | #define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK |
72 | | /* these queries are executed against the FK (referencing) table: */ |
73 | 0 | #define RI_PLAN_CASCADE_ONDELETE 3 |
74 | 0 | #define RI_PLAN_CASCADE_ONUPDATE 4 |
75 | 0 | #define RI_PLAN_NO_ACTION 5 |
76 | | /* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */ |
77 | 0 | #define RI_PLAN_RESTRICT 6 |
78 | 0 | #define RI_PLAN_SETNULL_ONDELETE 7 |
79 | 0 | #define RI_PLAN_SETNULL_ONUPDATE 8 |
80 | 0 | #define RI_PLAN_SETDEFAULT_ONDELETE 9 |
81 | 0 | #define RI_PLAN_SETDEFAULT_ONUPDATE 10 |
82 | | |
83 | | #define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3) |
84 | | #define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2) |
85 | | |
86 | 0 | #define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum)) |
87 | 0 | #define RIAttType(rel, attnum) attnumTypeId(rel, attnum) |
88 | 0 | #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum) |
89 | | |
90 | 0 | #define RI_TRIGTYPE_INSERT 1 |
91 | 0 | #define RI_TRIGTYPE_UPDATE 2 |
92 | 0 | #define RI_TRIGTYPE_DELETE 3 |
93 | | |
94 | | |
95 | | /* |
96 | | * RI_ConstraintInfo |
97 | | * |
98 | | * Information extracted from an FK pg_constraint entry. This is cached in |
99 | | * ri_constraint_cache. |
100 | | * |
101 | | * Note that pf/pp/ff_eq_oprs may hold the overlaps operator instead of equals |
102 | | * for the PERIOD part of a temporal foreign key. |
103 | | */ |
104 | | typedef struct RI_ConstraintInfo |
105 | | { |
106 | | Oid constraint_id; /* OID of pg_constraint entry (hash key) */ |
107 | | bool valid; /* successfully initialized? */ |
108 | | Oid constraint_root_id; /* OID of topmost ancestor constraint; |
109 | | * same as constraint_id if not inherited */ |
110 | | uint32 oidHashValue; /* hash value of constraint_id */ |
111 | | uint32 rootHashValue; /* hash value of constraint_root_id */ |
112 | | NameData conname; /* name of the FK constraint */ |
113 | | Oid pk_relid; /* referenced relation */ |
114 | | Oid fk_relid; /* referencing relation */ |
115 | | char confupdtype; /* foreign key's ON UPDATE action */ |
116 | | char confdeltype; /* foreign key's ON DELETE action */ |
117 | | int ndelsetcols; /* number of columns referenced in ON DELETE |
118 | | * SET clause */ |
119 | | int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on |
120 | | * delete */ |
121 | | char confmatchtype; /* foreign key's match type */ |
122 | | bool hasperiod; /* if the foreign key uses PERIOD */ |
123 | | int nkeys; /* number of key columns */ |
124 | | int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */ |
125 | | int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */ |
126 | | Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */ |
127 | | Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */ |
128 | | Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */ |
129 | | Oid period_contained_by_oper; /* anyrange <@ anyrange */ |
130 | | Oid agged_period_contained_by_oper; /* fkattr <@ range_agg(pkattr) */ |
131 | | Oid period_intersect_oper; /* anyrange * anyrange */ |
132 | | dlist_node valid_link; /* Link in list of valid entries */ |
133 | | } RI_ConstraintInfo; |
134 | | |
135 | | /* |
136 | | * RI_QueryKey |
137 | | * |
138 | | * The key identifying a prepared SPI plan in our query hashtable |
139 | | */ |
140 | | typedef struct RI_QueryKey |
141 | | { |
142 | | Oid constr_id; /* OID of pg_constraint entry */ |
143 | | int32 constr_queryno; /* query type ID, see RI_PLAN_XXX above */ |
144 | | } RI_QueryKey; |
145 | | |
146 | | /* |
147 | | * RI_QueryHashEntry |
148 | | */ |
149 | | typedef struct RI_QueryHashEntry |
150 | | { |
151 | | RI_QueryKey key; |
152 | | SPIPlanPtr plan; |
153 | | } RI_QueryHashEntry; |
154 | | |
155 | | /* |
156 | | * RI_CompareKey |
157 | | * |
158 | | * The key identifying an entry showing how to compare two values |
159 | | */ |
160 | | typedef struct RI_CompareKey |
161 | | { |
162 | | Oid eq_opr; /* the equality operator to apply */ |
163 | | Oid typeid; /* the data type to apply it to */ |
164 | | } RI_CompareKey; |
165 | | |
166 | | /* |
167 | | * RI_CompareHashEntry |
168 | | */ |
169 | | typedef struct RI_CompareHashEntry |
170 | | { |
171 | | RI_CompareKey key; |
172 | | bool valid; /* successfully initialized? */ |
173 | | FmgrInfo eq_opr_finfo; /* call info for equality fn */ |
174 | | FmgrInfo cast_func_finfo; /* in case we must coerce input */ |
175 | | } RI_CompareHashEntry; |
176 | | |
177 | | |
178 | | /* |
179 | | * Local data |
180 | | */ |
181 | | static HTAB *ri_constraint_cache = NULL; |
182 | | static HTAB *ri_query_cache = NULL; |
183 | | static HTAB *ri_compare_cache = NULL; |
184 | | static dclist_head ri_constraint_cache_valid_list; |
185 | | |
186 | | |
187 | | /* |
188 | | * Local function prototypes |
189 | | */ |
190 | | static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, |
191 | | TupleTableSlot *oldslot, |
192 | | const RI_ConstraintInfo *riinfo); |
193 | | static Datum ri_restrict(TriggerData *trigdata, bool is_no_action); |
194 | | static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind); |
195 | | static void quoteOneName(char *buffer, const char *name); |
196 | | static void quoteRelationName(char *buffer, Relation rel); |
197 | | static void ri_GenerateQual(StringInfo buf, |
198 | | const char *sep, |
199 | | const char *leftop, Oid leftoptype, |
200 | | Oid opoid, |
201 | | const char *rightop, Oid rightoptype); |
202 | | static void ri_GenerateQualCollation(StringInfo buf, Oid collation); |
203 | | static int ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot, |
204 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk); |
205 | | static void ri_BuildQueryKey(RI_QueryKey *key, |
206 | | const RI_ConstraintInfo *riinfo, |
207 | | int32 constr_queryno); |
208 | | static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, |
209 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk); |
210 | | static bool ri_CompareWithCast(Oid eq_opr, Oid typeid, Oid collid, |
211 | | Datum lhs, Datum rhs); |
212 | | |
213 | | static void ri_InitHashTables(void); |
214 | | static void InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue); |
215 | | static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key); |
216 | | static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan); |
217 | | static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid); |
218 | | |
219 | | static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, |
220 | | int tgkind); |
221 | | static const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger, |
222 | | Relation trig_rel, bool rel_is_pk); |
223 | | static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid); |
224 | | static Oid get_ri_constraint_root(Oid constrOid); |
225 | | static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, |
226 | | RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel); |
227 | | static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo, |
228 | | RI_QueryKey *qkey, SPIPlanPtr qplan, |
229 | | Relation fk_rel, Relation pk_rel, |
230 | | TupleTableSlot *oldslot, TupleTableSlot *newslot, |
231 | | bool is_restrict, |
232 | | bool detectNewRows, int expect_OK); |
233 | | static void ri_ExtractValues(Relation rel, TupleTableSlot *slot, |
234 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk, |
235 | | Datum *vals, char *nulls); |
236 | | pg_noreturn static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, |
237 | | Relation pk_rel, Relation fk_rel, |
238 | | TupleTableSlot *violatorslot, TupleDesc tupdesc, |
239 | | int queryno, bool is_restrict, bool partgone); |
240 | | |
241 | | |
242 | | /* |
243 | | * RI_FKey_check - |
244 | | * |
245 | | * Check foreign key existence (combined for INSERT and UPDATE). |
246 | | */ |
247 | | static Datum |
248 | | RI_FKey_check(TriggerData *trigdata) |
249 | 0 | { |
250 | 0 | const RI_ConstraintInfo *riinfo; |
251 | 0 | Relation fk_rel; |
252 | 0 | Relation pk_rel; |
253 | 0 | TupleTableSlot *newslot; |
254 | 0 | RI_QueryKey qkey; |
255 | 0 | SPIPlanPtr qplan; |
256 | |
|
257 | 0 | riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, |
258 | 0 | trigdata->tg_relation, false); |
259 | |
|
260 | 0 | if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) |
261 | 0 | newslot = trigdata->tg_newslot; |
262 | 0 | else |
263 | 0 | newslot = trigdata->tg_trigslot; |
264 | | |
265 | | /* |
266 | | * We should not even consider checking the row if it is no longer valid, |
267 | | * since it was either deleted (so the deferred check should be skipped) |
268 | | * or updated (in which case only the latest version of the row should be |
269 | | * checked). Test its liveness according to SnapshotSelf. We need pin |
270 | | * and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller |
271 | | * should be holding pin, but not lock. |
272 | | */ |
273 | 0 | if (!table_tuple_satisfies_snapshot(trigdata->tg_relation, newslot, SnapshotSelf)) |
274 | 0 | return PointerGetDatum(NULL); |
275 | | |
276 | | /* |
277 | | * Get the relation descriptors of the FK and PK tables. |
278 | | * |
279 | | * pk_rel is opened in RowShareLock mode since that's what our eventual |
280 | | * SELECT FOR KEY SHARE will get on it. |
281 | | */ |
282 | 0 | fk_rel = trigdata->tg_relation; |
283 | 0 | pk_rel = table_open(riinfo->pk_relid, RowShareLock); |
284 | |
|
285 | 0 | switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false)) |
286 | 0 | { |
287 | 0 | case RI_KEYS_ALL_NULL: |
288 | | |
289 | | /* |
290 | | * No further check needed - an all-NULL key passes every type of |
291 | | * foreign key constraint. |
292 | | */ |
293 | 0 | table_close(pk_rel, RowShareLock); |
294 | 0 | return PointerGetDatum(NULL); |
295 | | |
296 | 0 | case RI_KEYS_SOME_NULL: |
297 | | |
298 | | /* |
299 | | * This is the only case that differs between the three kinds of |
300 | | * MATCH. |
301 | | */ |
302 | 0 | switch (riinfo->confmatchtype) |
303 | 0 | { |
304 | 0 | case FKCONSTR_MATCH_FULL: |
305 | | |
306 | | /* |
307 | | * Not allowed - MATCH FULL says either all or none of the |
308 | | * attributes can be NULLs |
309 | | */ |
310 | 0 | ereport(ERROR, |
311 | 0 | (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), |
312 | 0 | errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", |
313 | 0 | RelationGetRelationName(fk_rel), |
314 | 0 | NameStr(riinfo->conname)), |
315 | 0 | errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), |
316 | 0 | errtableconstraint(fk_rel, |
317 | 0 | NameStr(riinfo->conname)))); |
318 | 0 | table_close(pk_rel, RowShareLock); |
319 | 0 | return PointerGetDatum(NULL); |
320 | | |
321 | 0 | case FKCONSTR_MATCH_SIMPLE: |
322 | | |
323 | | /* |
324 | | * MATCH SIMPLE - if ANY column is null, the key passes |
325 | | * the constraint. |
326 | | */ |
327 | 0 | table_close(pk_rel, RowShareLock); |
328 | 0 | return PointerGetDatum(NULL); |
329 | |
|
330 | | #ifdef NOT_USED |
331 | | case FKCONSTR_MATCH_PARTIAL: |
332 | | |
333 | | /* |
334 | | * MATCH PARTIAL - all non-null columns must match. (not |
335 | | * implemented, can be done by modifying the query below |
336 | | * to only include non-null columns, or by writing a |
337 | | * special version here) |
338 | | */ |
339 | | break; |
340 | | #endif |
341 | 0 | } |
342 | | |
343 | 0 | case RI_KEYS_NONE_NULL: |
344 | | |
345 | | /* |
346 | | * Have a full qualified key - continue below for all three kinds |
347 | | * of MATCH. |
348 | | */ |
349 | 0 | break; |
350 | 0 | } |
351 | | |
352 | 0 | SPI_connect(); |
353 | | |
354 | | /* Fetch or prepare a saved plan for the real check */ |
355 | 0 | ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK); |
356 | |
|
357 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
358 | 0 | { |
359 | 0 | StringInfoData querybuf; |
360 | 0 | char pkrelname[MAX_QUOTED_REL_NAME_LEN]; |
361 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
362 | 0 | char paramname[16]; |
363 | 0 | const char *querysep; |
364 | 0 | Oid queryoids[RI_MAX_NUMKEYS]; |
365 | 0 | const char *pk_only; |
366 | | |
367 | | /* ---------- |
368 | | * The query string built is |
369 | | * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...] |
370 | | * FOR KEY SHARE OF x |
371 | | * The type id's for the $ parameters are those of the |
372 | | * corresponding FK attributes. |
373 | | * |
374 | | * But for temporal FKs we need to make sure |
375 | | * the FK's range is completely covered. |
376 | | * So we use this query instead: |
377 | | * SELECT 1 |
378 | | * FROM ( |
379 | | * SELECT pkperiodatt AS r |
380 | | * FROM [ONLY] pktable x |
381 | | * WHERE pkatt1 = $1 [AND ...] |
382 | | * AND pkperiodatt && $n |
383 | | * FOR KEY SHARE OF x |
384 | | * ) x1 |
385 | | * HAVING $n <@ range_agg(x1.r) |
386 | | * Note if FOR KEY SHARE ever allows GROUP BY and HAVING |
387 | | * we can make this a bit simpler. |
388 | | * ---------- |
389 | | */ |
390 | 0 | initStringInfo(&querybuf); |
391 | 0 | pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
392 | 0 | "" : "ONLY "; |
393 | 0 | quoteRelationName(pkrelname, pk_rel); |
394 | 0 | if (riinfo->hasperiod) |
395 | 0 | { |
396 | 0 | quoteOneName(attname, |
397 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1])); |
398 | |
|
399 | 0 | appendStringInfo(&querybuf, |
400 | 0 | "SELECT 1 FROM (SELECT %s AS r FROM %s%s x", |
401 | 0 | attname, pk_only, pkrelname); |
402 | 0 | } |
403 | 0 | else |
404 | 0 | { |
405 | 0 | appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", |
406 | 0 | pk_only, pkrelname); |
407 | 0 | } |
408 | 0 | querysep = "WHERE"; |
409 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
410 | 0 | { |
411 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
412 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
413 | |
|
414 | 0 | quoteOneName(attname, |
415 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[i])); |
416 | 0 | sprintf(paramname, "$%d", i + 1); |
417 | 0 | ri_GenerateQual(&querybuf, querysep, |
418 | 0 | attname, pk_type, |
419 | 0 | riinfo->pf_eq_oprs[i], |
420 | 0 | paramname, fk_type); |
421 | 0 | querysep = "AND"; |
422 | 0 | queryoids[i] = fk_type; |
423 | 0 | } |
424 | 0 | appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); |
425 | 0 | if (riinfo->hasperiod) |
426 | 0 | { |
427 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]); |
428 | |
|
429 | 0 | appendStringInfoString(&querybuf, ") x1 HAVING "); |
430 | 0 | sprintf(paramname, "$%d", riinfo->nkeys); |
431 | 0 | ri_GenerateQual(&querybuf, "", |
432 | 0 | paramname, fk_type, |
433 | 0 | riinfo->agged_period_contained_by_oper, |
434 | 0 | "pg_catalog.range_agg", ANYMULTIRANGEOID); |
435 | 0 | appendStringInfoString(&querybuf, "(x1.r)"); |
436 | 0 | } |
437 | | |
438 | | /* Prepare and save the plan */ |
439 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, |
440 | 0 | &qkey, fk_rel, pk_rel); |
441 | 0 | } |
442 | | |
443 | | /* |
444 | | * Now check that foreign key exists in PK table |
445 | | * |
446 | | * XXX detectNewRows must be true when a partitioned table is on the |
447 | | * referenced side. The reason is that our snapshot must be fresh in |
448 | | * order for the hack in find_inheritance_children() to work. |
449 | | */ |
450 | 0 | ri_PerformCheck(riinfo, &qkey, qplan, |
451 | 0 | fk_rel, pk_rel, |
452 | 0 | NULL, newslot, |
453 | 0 | false, |
454 | 0 | pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE, |
455 | 0 | SPI_OK_SELECT); |
456 | |
|
457 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
458 | 0 | elog(ERROR, "SPI_finish failed"); |
459 | | |
460 | 0 | table_close(pk_rel, RowShareLock); |
461 | |
|
462 | 0 | return PointerGetDatum(NULL); |
463 | 0 | } |
464 | | |
465 | | |
466 | | /* |
467 | | * RI_FKey_check_ins - |
468 | | * |
469 | | * Check foreign key existence at insert event on FK table. |
470 | | */ |
471 | | Datum |
472 | | RI_FKey_check_ins(PG_FUNCTION_ARGS) |
473 | 0 | { |
474 | | /* Check that this is a valid trigger call on the right time and event. */ |
475 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT); |
476 | | |
477 | | /* Share code with UPDATE case. */ |
478 | 0 | return RI_FKey_check((TriggerData *) fcinfo->context); |
479 | 0 | } |
480 | | |
481 | | |
482 | | /* |
483 | | * RI_FKey_check_upd - |
484 | | * |
485 | | * Check foreign key existence at update event on FK table. |
486 | | */ |
487 | | Datum |
488 | | RI_FKey_check_upd(PG_FUNCTION_ARGS) |
489 | 0 | { |
490 | | /* Check that this is a valid trigger call on the right time and event. */ |
491 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE); |
492 | | |
493 | | /* Share code with INSERT case. */ |
494 | 0 | return RI_FKey_check((TriggerData *) fcinfo->context); |
495 | 0 | } |
496 | | |
497 | | |
498 | | /* |
499 | | * ri_Check_Pk_Match |
500 | | * |
501 | | * Check to see if another PK row has been created that provides the same |
502 | | * key values as the "oldslot" that's been modified or deleted in our trigger |
503 | | * event. Returns true if a match is found in the PK table. |
504 | | * |
505 | | * We assume the caller checked that the oldslot contains no NULL key values, |
506 | | * since otherwise a match is impossible. |
507 | | */ |
508 | | static bool |
509 | | ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, |
510 | | TupleTableSlot *oldslot, |
511 | | const RI_ConstraintInfo *riinfo) |
512 | 0 | { |
513 | 0 | SPIPlanPtr qplan; |
514 | 0 | RI_QueryKey qkey; |
515 | 0 | bool result; |
516 | | |
517 | | /* Only called for non-null rows */ |
518 | 0 | Assert(ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) == RI_KEYS_NONE_NULL); |
519 | |
|
520 | 0 | SPI_connect(); |
521 | | |
522 | | /* |
523 | | * Fetch or prepare a saved plan for checking PK table with values coming |
524 | | * from a PK row |
525 | | */ |
526 | 0 | ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK); |
527 | |
|
528 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
529 | 0 | { |
530 | 0 | StringInfoData querybuf; |
531 | 0 | char pkrelname[MAX_QUOTED_REL_NAME_LEN]; |
532 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
533 | 0 | char paramname[16]; |
534 | 0 | const char *querysep; |
535 | 0 | const char *pk_only; |
536 | 0 | Oid queryoids[RI_MAX_NUMKEYS]; |
537 | | |
538 | | /* ---------- |
539 | | * The query string built is |
540 | | * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...] |
541 | | * FOR KEY SHARE OF x |
542 | | * The type id's for the $ parameters are those of the |
543 | | * PK attributes themselves. |
544 | | * |
545 | | * But for temporal FKs we need to make sure |
546 | | * the old PK's range is completely covered. |
547 | | * So we use this query instead: |
548 | | * SELECT 1 |
549 | | * FROM ( |
550 | | * SELECT pkperiodatt AS r |
551 | | * FROM [ONLY] pktable x |
552 | | * WHERE pkatt1 = $1 [AND ...] |
553 | | * AND pkperiodatt && $n |
554 | | * FOR KEY SHARE OF x |
555 | | * ) x1 |
556 | | * HAVING $n <@ range_agg(x1.r) |
557 | | * Note if FOR KEY SHARE ever allows GROUP BY and HAVING |
558 | | * we can make this a bit simpler. |
559 | | * ---------- |
560 | | */ |
561 | 0 | initStringInfo(&querybuf); |
562 | 0 | pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
563 | 0 | "" : "ONLY "; |
564 | 0 | quoteRelationName(pkrelname, pk_rel); |
565 | 0 | if (riinfo->hasperiod) |
566 | 0 | { |
567 | 0 | quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1])); |
568 | |
|
569 | 0 | appendStringInfo(&querybuf, |
570 | 0 | "SELECT 1 FROM (SELECT %s AS r FROM %s%s x", |
571 | 0 | attname, pk_only, pkrelname); |
572 | 0 | } |
573 | 0 | else |
574 | 0 | { |
575 | 0 | appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", |
576 | 0 | pk_only, pkrelname); |
577 | 0 | } |
578 | 0 | querysep = "WHERE"; |
579 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
580 | 0 | { |
581 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
582 | |
|
583 | 0 | quoteOneName(attname, |
584 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[i])); |
585 | 0 | sprintf(paramname, "$%d", i + 1); |
586 | 0 | ri_GenerateQual(&querybuf, querysep, |
587 | 0 | attname, pk_type, |
588 | 0 | riinfo->pp_eq_oprs[i], |
589 | 0 | paramname, pk_type); |
590 | 0 | querysep = "AND"; |
591 | 0 | queryoids[i] = pk_type; |
592 | 0 | } |
593 | 0 | appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); |
594 | 0 | if (riinfo->hasperiod) |
595 | 0 | { |
596 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]); |
597 | |
|
598 | 0 | appendStringInfoString(&querybuf, ") x1 HAVING "); |
599 | 0 | sprintf(paramname, "$%d", riinfo->nkeys); |
600 | 0 | ri_GenerateQual(&querybuf, "", |
601 | 0 | paramname, fk_type, |
602 | 0 | riinfo->agged_period_contained_by_oper, |
603 | 0 | "pg_catalog.range_agg", ANYMULTIRANGEOID); |
604 | 0 | appendStringInfoString(&querybuf, "(x1.r)"); |
605 | 0 | } |
606 | | |
607 | | /* Prepare and save the plan */ |
608 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, |
609 | 0 | &qkey, fk_rel, pk_rel); |
610 | 0 | } |
611 | | |
612 | | /* |
613 | | * We have a plan now. Run it. |
614 | | */ |
615 | 0 | result = ri_PerformCheck(riinfo, &qkey, qplan, |
616 | 0 | fk_rel, pk_rel, |
617 | 0 | oldslot, NULL, |
618 | 0 | false, |
619 | 0 | true, /* treat like update */ |
620 | 0 | SPI_OK_SELECT); |
621 | |
|
622 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
623 | 0 | elog(ERROR, "SPI_finish failed"); |
624 | | |
625 | 0 | return result; |
626 | 0 | } |
627 | | |
628 | | |
629 | | /* |
630 | | * RI_FKey_noaction_del - |
631 | | * |
632 | | * Give an error and roll back the current transaction if the |
633 | | * delete has resulted in a violation of the given referential |
634 | | * integrity constraint. |
635 | | */ |
636 | | Datum |
637 | | RI_FKey_noaction_del(PG_FUNCTION_ARGS) |
638 | 0 | { |
639 | | /* Check that this is a valid trigger call on the right time and event. */ |
640 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE); |
641 | | |
642 | | /* Share code with RESTRICT/UPDATE cases. */ |
643 | 0 | return ri_restrict((TriggerData *) fcinfo->context, true); |
644 | 0 | } |
645 | | |
646 | | /* |
647 | | * RI_FKey_restrict_del - |
648 | | * |
649 | | * Restrict delete from PK table to rows unreferenced by foreign key. |
650 | | * |
651 | | * The SQL standard intends that this referential action occur exactly when |
652 | | * the delete is performed, rather than after. This appears to be |
653 | | * the only difference between "NO ACTION" and "RESTRICT". In Postgres |
654 | | * we still implement this as an AFTER trigger, but it's non-deferrable. |
655 | | */ |
656 | | Datum |
657 | | RI_FKey_restrict_del(PG_FUNCTION_ARGS) |
658 | 0 | { |
659 | | /* Check that this is a valid trigger call on the right time and event. */ |
660 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE); |
661 | | |
662 | | /* Share code with NO ACTION/UPDATE cases. */ |
663 | 0 | return ri_restrict((TriggerData *) fcinfo->context, false); |
664 | 0 | } |
665 | | |
666 | | /* |
667 | | * RI_FKey_noaction_upd - |
668 | | * |
669 | | * Give an error and roll back the current transaction if the |
670 | | * update has resulted in a violation of the given referential |
671 | | * integrity constraint. |
672 | | */ |
673 | | Datum |
674 | | RI_FKey_noaction_upd(PG_FUNCTION_ARGS) |
675 | 0 | { |
676 | | /* Check that this is a valid trigger call on the right time and event. */ |
677 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE); |
678 | | |
679 | | /* Share code with RESTRICT/DELETE cases. */ |
680 | 0 | return ri_restrict((TriggerData *) fcinfo->context, true); |
681 | 0 | } |
682 | | |
683 | | /* |
684 | | * RI_FKey_restrict_upd - |
685 | | * |
686 | | * Restrict update of PK to rows unreferenced by foreign key. |
687 | | * |
688 | | * The SQL standard intends that this referential action occur exactly when |
689 | | * the update is performed, rather than after. This appears to be |
690 | | * the only difference between "NO ACTION" and "RESTRICT". In Postgres |
691 | | * we still implement this as an AFTER trigger, but it's non-deferrable. |
692 | | */ |
693 | | Datum |
694 | | RI_FKey_restrict_upd(PG_FUNCTION_ARGS) |
695 | 0 | { |
696 | | /* Check that this is a valid trigger call on the right time and event. */ |
697 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE); |
698 | | |
699 | | /* Share code with NO ACTION/DELETE cases. */ |
700 | 0 | return ri_restrict((TriggerData *) fcinfo->context, false); |
701 | 0 | } |
702 | | |
703 | | /* |
704 | | * ri_restrict - |
705 | | * |
706 | | * Common code for ON DELETE RESTRICT, ON DELETE NO ACTION, |
707 | | * ON UPDATE RESTRICT, and ON UPDATE NO ACTION. |
708 | | */ |
709 | | static Datum |
710 | | ri_restrict(TriggerData *trigdata, bool is_no_action) |
711 | 0 | { |
712 | 0 | const RI_ConstraintInfo *riinfo; |
713 | 0 | Relation fk_rel; |
714 | 0 | Relation pk_rel; |
715 | 0 | TupleTableSlot *oldslot; |
716 | 0 | RI_QueryKey qkey; |
717 | 0 | SPIPlanPtr qplan; |
718 | |
|
719 | 0 | riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, |
720 | 0 | trigdata->tg_relation, true); |
721 | | |
722 | | /* |
723 | | * Get the relation descriptors of the FK and PK tables and the old tuple. |
724 | | * |
725 | | * fk_rel is opened in RowShareLock mode since that's what our eventual |
726 | | * SELECT FOR KEY SHARE will get on it. |
727 | | */ |
728 | 0 | fk_rel = table_open(riinfo->fk_relid, RowShareLock); |
729 | 0 | pk_rel = trigdata->tg_relation; |
730 | 0 | oldslot = trigdata->tg_trigslot; |
731 | | |
732 | | /* |
733 | | * If another PK row now exists providing the old key values, we should |
734 | | * not do anything. However, this check should only be made in the NO |
735 | | * ACTION case; in RESTRICT cases we don't wish to allow another row to be |
736 | | * substituted. |
737 | | * |
738 | | * If the foreign key has PERIOD, we incorporate looking for replacement |
739 | | * rows in the main SQL query below, so we needn't do it here. |
740 | | */ |
741 | 0 | if (is_no_action && !riinfo->hasperiod && |
742 | 0 | ri_Check_Pk_Match(pk_rel, fk_rel, oldslot, riinfo)) |
743 | 0 | { |
744 | 0 | table_close(fk_rel, RowShareLock); |
745 | 0 | return PointerGetDatum(NULL); |
746 | 0 | } |
747 | | |
748 | 0 | SPI_connect(); |
749 | | |
750 | | /* |
751 | | * Fetch or prepare a saved plan for the restrict lookup (it's the same |
752 | | * query for delete and update cases) |
753 | | */ |
754 | 0 | ri_BuildQueryKey(&qkey, riinfo, is_no_action ? RI_PLAN_NO_ACTION : RI_PLAN_RESTRICT); |
755 | |
|
756 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
757 | 0 | { |
758 | 0 | StringInfoData querybuf; |
759 | 0 | char pkrelname[MAX_QUOTED_REL_NAME_LEN]; |
760 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
761 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
762 | 0 | char periodattname[MAX_QUOTED_NAME_LEN]; |
763 | 0 | char paramname[16]; |
764 | 0 | const char *querysep; |
765 | 0 | Oid queryoids[RI_MAX_NUMKEYS]; |
766 | 0 | const char *fk_only; |
767 | | |
768 | | /* ---------- |
769 | | * The query string built is |
770 | | * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...] |
771 | | * FOR KEY SHARE OF x |
772 | | * The type id's for the $ parameters are those of the |
773 | | * corresponding PK attributes. |
774 | | * ---------- |
775 | | */ |
776 | 0 | initStringInfo(&querybuf); |
777 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
778 | 0 | "" : "ONLY "; |
779 | 0 | quoteRelationName(fkrelname, fk_rel); |
780 | 0 | appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x", |
781 | 0 | fk_only, fkrelname); |
782 | 0 | querysep = "WHERE"; |
783 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
784 | 0 | { |
785 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
786 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
787 | |
|
788 | 0 | quoteOneName(attname, |
789 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
790 | 0 | sprintf(paramname, "$%d", i + 1); |
791 | 0 | ri_GenerateQual(&querybuf, querysep, |
792 | 0 | paramname, pk_type, |
793 | 0 | riinfo->pf_eq_oprs[i], |
794 | 0 | attname, fk_type); |
795 | 0 | querysep = "AND"; |
796 | 0 | queryoids[i] = pk_type; |
797 | 0 | } |
798 | | |
799 | | /*---------- |
800 | | * For temporal foreign keys, a reference could still be valid if the |
801 | | * referenced range didn't change too much. Also if a referencing |
802 | | * range extends past the current PK row, we don't want to check that |
803 | | * part: some other PK row should fulfill it. We only want to check |
804 | | * the part matching the PK record we've changed. Therefore to find |
805 | | * invalid records we do this: |
806 | | * |
807 | | * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = x.fkatt1 [AND ...] |
808 | | * -- begin temporal |
809 | | * AND $n && x.fkperiod |
810 | | * AND NOT coalesce((x.fkperiod * $n) <@ |
811 | | * (SELECT range_agg(r) |
812 | | * FROM (SELECT y.pkperiod r |
813 | | * FROM [ONLY] <pktable> y |
814 | | * WHERE $1 = y.pkatt1 [AND ...] AND $n && y.pkperiod |
815 | | * FOR KEY SHARE OF y) y2), false) |
816 | | * -- end temporal |
817 | | * FOR KEY SHARE OF x |
818 | | * |
819 | | * We need the coalesce in case the first subquery returns no rows. |
820 | | * We need the second subquery because FOR KEY SHARE doesn't support |
821 | | * aggregate queries. |
822 | | */ |
823 | 0 | if (riinfo->hasperiod && is_no_action) |
824 | 0 | { |
825 | 0 | Oid pk_period_type = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]); |
826 | 0 | Oid fk_period_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]); |
827 | 0 | StringInfoData intersectbuf; |
828 | 0 | StringInfoData replacementsbuf; |
829 | 0 | char *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
830 | 0 | "" : "ONLY "; |
831 | |
|
832 | 0 | quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1])); |
833 | 0 | sprintf(paramname, "$%d", riinfo->nkeys); |
834 | |
|
835 | 0 | appendStringInfoString(&querybuf, " AND NOT coalesce("); |
836 | | |
837 | | /* Intersect the fk with the old pk range */ |
838 | 0 | initStringInfo(&intersectbuf); |
839 | 0 | appendStringInfoChar(&intersectbuf, '('); |
840 | 0 | ri_GenerateQual(&intersectbuf, "", |
841 | 0 | attname, fk_period_type, |
842 | 0 | riinfo->period_intersect_oper, |
843 | 0 | paramname, pk_period_type); |
844 | 0 | appendStringInfoChar(&intersectbuf, ')'); |
845 | | |
846 | | /* Find the remaining history */ |
847 | 0 | initStringInfo(&replacementsbuf); |
848 | 0 | appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM "); |
849 | |
|
850 | 0 | quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1])); |
851 | 0 | quoteRelationName(pkrelname, pk_rel); |
852 | 0 | appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y", |
853 | 0 | periodattname, pk_only, pkrelname); |
854 | | |
855 | | /* Restrict pk rows to what matches */ |
856 | 0 | querysep = "WHERE"; |
857 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
858 | 0 | { |
859 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
860 | |
|
861 | 0 | quoteOneName(attname, |
862 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[i])); |
863 | 0 | sprintf(paramname, "$%d", i + 1); |
864 | 0 | ri_GenerateQual(&replacementsbuf, querysep, |
865 | 0 | paramname, pk_type, |
866 | 0 | riinfo->pp_eq_oprs[i], |
867 | 0 | attname, pk_type); |
868 | 0 | querysep = "AND"; |
869 | 0 | queryoids[i] = pk_type; |
870 | 0 | } |
871 | 0 | appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)"); |
872 | |
|
873 | 0 | ri_GenerateQual(&querybuf, "", |
874 | 0 | intersectbuf.data, fk_period_type, |
875 | 0 | riinfo->agged_period_contained_by_oper, |
876 | 0 | replacementsbuf.data, ANYMULTIRANGEOID); |
877 | | /* end of coalesce: */ |
878 | 0 | appendStringInfoString(&querybuf, ", false)"); |
879 | 0 | } |
880 | |
|
881 | 0 | appendStringInfoString(&querybuf, " FOR KEY SHARE OF x"); |
882 | | |
883 | | /* Prepare and save the plan */ |
884 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, |
885 | 0 | &qkey, fk_rel, pk_rel); |
886 | 0 | } |
887 | | |
888 | | /* |
889 | | * We have a plan now. Run it to check for existing references. |
890 | | */ |
891 | 0 | ri_PerformCheck(riinfo, &qkey, qplan, |
892 | 0 | fk_rel, pk_rel, |
893 | 0 | oldslot, NULL, |
894 | 0 | !is_no_action, |
895 | 0 | true, /* must detect new rows */ |
896 | 0 | SPI_OK_SELECT); |
897 | |
|
898 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
899 | 0 | elog(ERROR, "SPI_finish failed"); |
900 | | |
901 | 0 | table_close(fk_rel, RowShareLock); |
902 | |
|
903 | 0 | return PointerGetDatum(NULL); |
904 | 0 | } |
905 | | |
906 | | |
907 | | /* |
908 | | * RI_FKey_cascade_del - |
909 | | * |
910 | | * Cascaded delete foreign key references at delete event on PK table. |
911 | | */ |
912 | | Datum |
913 | | RI_FKey_cascade_del(PG_FUNCTION_ARGS) |
914 | 0 | { |
915 | 0 | TriggerData *trigdata = (TriggerData *) fcinfo->context; |
916 | 0 | const RI_ConstraintInfo *riinfo; |
917 | 0 | Relation fk_rel; |
918 | 0 | Relation pk_rel; |
919 | 0 | TupleTableSlot *oldslot; |
920 | 0 | RI_QueryKey qkey; |
921 | 0 | SPIPlanPtr qplan; |
922 | | |
923 | | /* Check that this is a valid trigger call on the right time and event. */ |
924 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE); |
925 | |
|
926 | 0 | riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, |
927 | 0 | trigdata->tg_relation, true); |
928 | | |
929 | | /* |
930 | | * Get the relation descriptors of the FK and PK tables and the old tuple. |
931 | | * |
932 | | * fk_rel is opened in RowExclusiveLock mode since that's what our |
933 | | * eventual DELETE will get on it. |
934 | | */ |
935 | 0 | fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); |
936 | 0 | pk_rel = trigdata->tg_relation; |
937 | 0 | oldslot = trigdata->tg_trigslot; |
938 | |
|
939 | 0 | SPI_connect(); |
940 | | |
941 | | /* Fetch or prepare a saved plan for the cascaded delete */ |
942 | 0 | ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE); |
943 | |
|
944 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
945 | 0 | { |
946 | 0 | StringInfoData querybuf; |
947 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
948 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
949 | 0 | char paramname[16]; |
950 | 0 | const char *querysep; |
951 | 0 | Oid queryoids[RI_MAX_NUMKEYS]; |
952 | 0 | const char *fk_only; |
953 | | |
954 | | /* ---------- |
955 | | * The query string built is |
956 | | * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...] |
957 | | * The type id's for the $ parameters are those of the |
958 | | * corresponding PK attributes. |
959 | | * ---------- |
960 | | */ |
961 | 0 | initStringInfo(&querybuf); |
962 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
963 | 0 | "" : "ONLY "; |
964 | 0 | quoteRelationName(fkrelname, fk_rel); |
965 | 0 | appendStringInfo(&querybuf, "DELETE FROM %s%s", |
966 | 0 | fk_only, fkrelname); |
967 | 0 | querysep = "WHERE"; |
968 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
969 | 0 | { |
970 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
971 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
972 | |
|
973 | 0 | quoteOneName(attname, |
974 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
975 | 0 | sprintf(paramname, "$%d", i + 1); |
976 | 0 | ri_GenerateQual(&querybuf, querysep, |
977 | 0 | paramname, pk_type, |
978 | 0 | riinfo->pf_eq_oprs[i], |
979 | 0 | attname, fk_type); |
980 | 0 | querysep = "AND"; |
981 | 0 | queryoids[i] = pk_type; |
982 | 0 | } |
983 | | |
984 | | /* Prepare and save the plan */ |
985 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, |
986 | 0 | &qkey, fk_rel, pk_rel); |
987 | 0 | } |
988 | | |
989 | | /* |
990 | | * We have a plan now. Build up the arguments from the key values in the |
991 | | * deleted PK tuple and delete the referencing rows |
992 | | */ |
993 | 0 | ri_PerformCheck(riinfo, &qkey, qplan, |
994 | 0 | fk_rel, pk_rel, |
995 | 0 | oldslot, NULL, |
996 | 0 | false, |
997 | 0 | true, /* must detect new rows */ |
998 | 0 | SPI_OK_DELETE); |
999 | |
|
1000 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
1001 | 0 | elog(ERROR, "SPI_finish failed"); |
1002 | | |
1003 | 0 | table_close(fk_rel, RowExclusiveLock); |
1004 | |
|
1005 | 0 | return PointerGetDatum(NULL); |
1006 | 0 | } |
1007 | | |
1008 | | |
1009 | | /* |
1010 | | * RI_FKey_cascade_upd - |
1011 | | * |
1012 | | * Cascaded update foreign key references at update event on PK table. |
1013 | | */ |
1014 | | Datum |
1015 | | RI_FKey_cascade_upd(PG_FUNCTION_ARGS) |
1016 | 0 | { |
1017 | 0 | TriggerData *trigdata = (TriggerData *) fcinfo->context; |
1018 | 0 | const RI_ConstraintInfo *riinfo; |
1019 | 0 | Relation fk_rel; |
1020 | 0 | Relation pk_rel; |
1021 | 0 | TupleTableSlot *newslot; |
1022 | 0 | TupleTableSlot *oldslot; |
1023 | 0 | RI_QueryKey qkey; |
1024 | 0 | SPIPlanPtr qplan; |
1025 | | |
1026 | | /* Check that this is a valid trigger call on the right time and event. */ |
1027 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE); |
1028 | |
|
1029 | 0 | riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, |
1030 | 0 | trigdata->tg_relation, true); |
1031 | | |
1032 | | /* |
1033 | | * Get the relation descriptors of the FK and PK tables and the new and |
1034 | | * old tuple. |
1035 | | * |
1036 | | * fk_rel is opened in RowExclusiveLock mode since that's what our |
1037 | | * eventual UPDATE will get on it. |
1038 | | */ |
1039 | 0 | fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); |
1040 | 0 | pk_rel = trigdata->tg_relation; |
1041 | 0 | newslot = trigdata->tg_newslot; |
1042 | 0 | oldslot = trigdata->tg_trigslot; |
1043 | |
|
1044 | 0 | SPI_connect(); |
1045 | | |
1046 | | /* Fetch or prepare a saved plan for the cascaded update */ |
1047 | 0 | ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE); |
1048 | |
|
1049 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
1050 | 0 | { |
1051 | 0 | StringInfoData querybuf; |
1052 | 0 | StringInfoData qualbuf; |
1053 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1054 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
1055 | 0 | char paramname[16]; |
1056 | 0 | const char *querysep; |
1057 | 0 | const char *qualsep; |
1058 | 0 | Oid queryoids[RI_MAX_NUMKEYS * 2]; |
1059 | 0 | const char *fk_only; |
1060 | | |
1061 | | /* ---------- |
1062 | | * The query string built is |
1063 | | * UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...] |
1064 | | * WHERE $n = fkatt1 [AND ...] |
1065 | | * The type id's for the $ parameters are those of the |
1066 | | * corresponding PK attributes. Note that we are assuming |
1067 | | * there is an assignment cast from the PK to the FK type; |
1068 | | * else the parser will fail. |
1069 | | * ---------- |
1070 | | */ |
1071 | 0 | initStringInfo(&querybuf); |
1072 | 0 | initStringInfo(&qualbuf); |
1073 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
1074 | 0 | "" : "ONLY "; |
1075 | 0 | quoteRelationName(fkrelname, fk_rel); |
1076 | 0 | appendStringInfo(&querybuf, "UPDATE %s%s SET", |
1077 | 0 | fk_only, fkrelname); |
1078 | 0 | querysep = ""; |
1079 | 0 | qualsep = "WHERE"; |
1080 | 0 | for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++) |
1081 | 0 | { |
1082 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
1083 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
1084 | |
|
1085 | 0 | quoteOneName(attname, |
1086 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1087 | 0 | appendStringInfo(&querybuf, |
1088 | 0 | "%s %s = $%d", |
1089 | 0 | querysep, attname, i + 1); |
1090 | 0 | sprintf(paramname, "$%d", j + 1); |
1091 | 0 | ri_GenerateQual(&qualbuf, qualsep, |
1092 | 0 | paramname, pk_type, |
1093 | 0 | riinfo->pf_eq_oprs[i], |
1094 | 0 | attname, fk_type); |
1095 | 0 | querysep = ","; |
1096 | 0 | qualsep = "AND"; |
1097 | 0 | queryoids[i] = pk_type; |
1098 | 0 | queryoids[j] = pk_type; |
1099 | 0 | } |
1100 | 0 | appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len); |
1101 | | |
1102 | | /* Prepare and save the plan */ |
1103 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids, |
1104 | 0 | &qkey, fk_rel, pk_rel); |
1105 | 0 | } |
1106 | | |
1107 | | /* |
1108 | | * We have a plan now. Run it to update the existing references. |
1109 | | */ |
1110 | 0 | ri_PerformCheck(riinfo, &qkey, qplan, |
1111 | 0 | fk_rel, pk_rel, |
1112 | 0 | oldslot, newslot, |
1113 | 0 | false, |
1114 | 0 | true, /* must detect new rows */ |
1115 | 0 | SPI_OK_UPDATE); |
1116 | |
|
1117 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
1118 | 0 | elog(ERROR, "SPI_finish failed"); |
1119 | | |
1120 | 0 | table_close(fk_rel, RowExclusiveLock); |
1121 | |
|
1122 | 0 | return PointerGetDatum(NULL); |
1123 | 0 | } |
1124 | | |
1125 | | |
1126 | | /* |
1127 | | * RI_FKey_setnull_del - |
1128 | | * |
1129 | | * Set foreign key references to NULL values at delete event on PK table. |
1130 | | */ |
1131 | | Datum |
1132 | | RI_FKey_setnull_del(PG_FUNCTION_ARGS) |
1133 | 0 | { |
1134 | | /* Check that this is a valid trigger call on the right time and event. */ |
1135 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE); |
1136 | | |
1137 | | /* Share code with UPDATE case */ |
1138 | 0 | return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE); |
1139 | 0 | } |
1140 | | |
1141 | | /* |
1142 | | * RI_FKey_setnull_upd - |
1143 | | * |
1144 | | * Set foreign key references to NULL at update event on PK table. |
1145 | | */ |
1146 | | Datum |
1147 | | RI_FKey_setnull_upd(PG_FUNCTION_ARGS) |
1148 | 0 | { |
1149 | | /* Check that this is a valid trigger call on the right time and event. */ |
1150 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE); |
1151 | | |
1152 | | /* Share code with DELETE case */ |
1153 | 0 | return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE); |
1154 | 0 | } |
1155 | | |
1156 | | /* |
1157 | | * RI_FKey_setdefault_del - |
1158 | | * |
1159 | | * Set foreign key references to defaults at delete event on PK table. |
1160 | | */ |
1161 | | Datum |
1162 | | RI_FKey_setdefault_del(PG_FUNCTION_ARGS) |
1163 | 0 | { |
1164 | | /* Check that this is a valid trigger call on the right time and event. */ |
1165 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE); |
1166 | | |
1167 | | /* Share code with UPDATE case */ |
1168 | 0 | return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE); |
1169 | 0 | } |
1170 | | |
1171 | | /* |
1172 | | * RI_FKey_setdefault_upd - |
1173 | | * |
1174 | | * Set foreign key references to defaults at update event on PK table. |
1175 | | */ |
1176 | | Datum |
1177 | | RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) |
1178 | 0 | { |
1179 | | /* Check that this is a valid trigger call on the right time and event. */ |
1180 | 0 | ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE); |
1181 | | |
1182 | | /* Share code with DELETE case */ |
1183 | 0 | return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE); |
1184 | 0 | } |
1185 | | |
1186 | | /* |
1187 | | * ri_set - |
1188 | | * |
1189 | | * Common code for ON DELETE SET NULL, ON DELETE SET DEFAULT, ON UPDATE SET |
1190 | | * NULL, and ON UPDATE SET DEFAULT. |
1191 | | */ |
1192 | | static Datum |
1193 | | ri_set(TriggerData *trigdata, bool is_set_null, int tgkind) |
1194 | 0 | { |
1195 | 0 | const RI_ConstraintInfo *riinfo; |
1196 | 0 | Relation fk_rel; |
1197 | 0 | Relation pk_rel; |
1198 | 0 | TupleTableSlot *oldslot; |
1199 | 0 | RI_QueryKey qkey; |
1200 | 0 | SPIPlanPtr qplan; |
1201 | 0 | int32 queryno; |
1202 | |
|
1203 | 0 | riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger, |
1204 | 0 | trigdata->tg_relation, true); |
1205 | | |
1206 | | /* |
1207 | | * Get the relation descriptors of the FK and PK tables and the old tuple. |
1208 | | * |
1209 | | * fk_rel is opened in RowExclusiveLock mode since that's what our |
1210 | | * eventual UPDATE will get on it. |
1211 | | */ |
1212 | 0 | fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); |
1213 | 0 | pk_rel = trigdata->tg_relation; |
1214 | 0 | oldslot = trigdata->tg_trigslot; |
1215 | |
|
1216 | 0 | SPI_connect(); |
1217 | | |
1218 | | /* |
1219 | | * Fetch or prepare a saved plan for the trigger. |
1220 | | */ |
1221 | 0 | switch (tgkind) |
1222 | 0 | { |
1223 | 0 | case RI_TRIGTYPE_UPDATE: |
1224 | 0 | queryno = is_set_null |
1225 | 0 | ? RI_PLAN_SETNULL_ONUPDATE |
1226 | 0 | : RI_PLAN_SETDEFAULT_ONUPDATE; |
1227 | 0 | break; |
1228 | 0 | case RI_TRIGTYPE_DELETE: |
1229 | 0 | queryno = is_set_null |
1230 | 0 | ? RI_PLAN_SETNULL_ONDELETE |
1231 | 0 | : RI_PLAN_SETDEFAULT_ONDELETE; |
1232 | 0 | break; |
1233 | 0 | default: |
1234 | 0 | elog(ERROR, "invalid tgkind passed to ri_set"); |
1235 | 0 | } |
1236 | | |
1237 | 0 | ri_BuildQueryKey(&qkey, riinfo, queryno); |
1238 | |
|
1239 | 0 | if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) |
1240 | 0 | { |
1241 | 0 | StringInfoData querybuf; |
1242 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1243 | 0 | char attname[MAX_QUOTED_NAME_LEN]; |
1244 | 0 | char paramname[16]; |
1245 | 0 | const char *querysep; |
1246 | 0 | const char *qualsep; |
1247 | 0 | Oid queryoids[RI_MAX_NUMKEYS]; |
1248 | 0 | const char *fk_only; |
1249 | 0 | int num_cols_to_set; |
1250 | 0 | const int16 *set_cols; |
1251 | |
|
1252 | 0 | switch (tgkind) |
1253 | 0 | { |
1254 | 0 | case RI_TRIGTYPE_UPDATE: |
1255 | 0 | num_cols_to_set = riinfo->nkeys; |
1256 | 0 | set_cols = riinfo->fk_attnums; |
1257 | 0 | break; |
1258 | 0 | case RI_TRIGTYPE_DELETE: |
1259 | | |
1260 | | /* |
1261 | | * If confdelsetcols are present, then we only update the |
1262 | | * columns specified in that array, otherwise we update all |
1263 | | * the referencing columns. |
1264 | | */ |
1265 | 0 | if (riinfo->ndelsetcols != 0) |
1266 | 0 | { |
1267 | 0 | num_cols_to_set = riinfo->ndelsetcols; |
1268 | 0 | set_cols = riinfo->confdelsetcols; |
1269 | 0 | } |
1270 | 0 | else |
1271 | 0 | { |
1272 | 0 | num_cols_to_set = riinfo->nkeys; |
1273 | 0 | set_cols = riinfo->fk_attnums; |
1274 | 0 | } |
1275 | 0 | break; |
1276 | 0 | default: |
1277 | 0 | elog(ERROR, "invalid tgkind passed to ri_set"); |
1278 | 0 | } |
1279 | | |
1280 | | /* ---------- |
1281 | | * The query string built is |
1282 | | * UPDATE [ONLY] <fktable> SET fkatt1 = {NULL|DEFAULT} [, ...] |
1283 | | * WHERE $1 = fkatt1 [AND ...] |
1284 | | * The type id's for the $ parameters are those of the |
1285 | | * corresponding PK attributes. |
1286 | | * ---------- |
1287 | | */ |
1288 | 0 | initStringInfo(&querybuf); |
1289 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
1290 | 0 | "" : "ONLY "; |
1291 | 0 | quoteRelationName(fkrelname, fk_rel); |
1292 | 0 | appendStringInfo(&querybuf, "UPDATE %s%s SET", |
1293 | 0 | fk_only, fkrelname); |
1294 | | |
1295 | | /* |
1296 | | * Add assignment clauses |
1297 | | */ |
1298 | 0 | querysep = ""; |
1299 | 0 | for (int i = 0; i < num_cols_to_set; i++) |
1300 | 0 | { |
1301 | 0 | quoteOneName(attname, RIAttName(fk_rel, set_cols[i])); |
1302 | 0 | appendStringInfo(&querybuf, |
1303 | 0 | "%s %s = %s", |
1304 | 0 | querysep, attname, |
1305 | 0 | is_set_null ? "NULL" : "DEFAULT"); |
1306 | 0 | querysep = ","; |
1307 | 0 | } |
1308 | | |
1309 | | /* |
1310 | | * Add WHERE clause |
1311 | | */ |
1312 | 0 | qualsep = "WHERE"; |
1313 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
1314 | 0 | { |
1315 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
1316 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
1317 | |
|
1318 | 0 | quoteOneName(attname, |
1319 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1320 | |
|
1321 | 0 | sprintf(paramname, "$%d", i + 1); |
1322 | 0 | ri_GenerateQual(&querybuf, qualsep, |
1323 | 0 | paramname, pk_type, |
1324 | 0 | riinfo->pf_eq_oprs[i], |
1325 | 0 | attname, fk_type); |
1326 | 0 | qualsep = "AND"; |
1327 | 0 | queryoids[i] = pk_type; |
1328 | 0 | } |
1329 | | |
1330 | | /* Prepare and save the plan */ |
1331 | 0 | qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids, |
1332 | 0 | &qkey, fk_rel, pk_rel); |
1333 | 0 | } |
1334 | | |
1335 | | /* |
1336 | | * We have a plan now. Run it to update the existing references. |
1337 | | */ |
1338 | 0 | ri_PerformCheck(riinfo, &qkey, qplan, |
1339 | 0 | fk_rel, pk_rel, |
1340 | 0 | oldslot, NULL, |
1341 | 0 | false, |
1342 | 0 | true, /* must detect new rows */ |
1343 | 0 | SPI_OK_UPDATE); |
1344 | |
|
1345 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
1346 | 0 | elog(ERROR, "SPI_finish failed"); |
1347 | | |
1348 | 0 | table_close(fk_rel, RowExclusiveLock); |
1349 | |
|
1350 | 0 | if (is_set_null) |
1351 | 0 | return PointerGetDatum(NULL); |
1352 | 0 | else |
1353 | 0 | { |
1354 | | /* |
1355 | | * If we just deleted or updated the PK row whose key was equal to the |
1356 | | * FK columns' default values, and a referencing row exists in the FK |
1357 | | * table, we would have updated that row to the same values it already |
1358 | | * had --- and RI_FKey_fk_upd_check_required would hence believe no |
1359 | | * check is necessary. So we need to do another lookup now and in |
1360 | | * case a reference still exists, abort the operation. That is |
1361 | | * already implemented in the NO ACTION trigger, so just run it. (This |
1362 | | * recheck is only needed in the SET DEFAULT case, since CASCADE would |
1363 | | * remove such rows in case of a DELETE operation or would change the |
1364 | | * FK key values in case of an UPDATE, while SET NULL is certain to |
1365 | | * result in rows that satisfy the FK constraint.) |
1366 | | */ |
1367 | 0 | return ri_restrict(trigdata, true); |
1368 | 0 | } |
1369 | 0 | } |
1370 | | |
1371 | | |
1372 | | /* |
1373 | | * RI_FKey_pk_upd_check_required - |
1374 | | * |
1375 | | * Check if we really need to fire the RI trigger for an update or delete to a PK |
1376 | | * relation. This is called by the AFTER trigger queue manager to see if |
1377 | | * it can skip queuing an instance of an RI trigger. Returns true if the |
1378 | | * trigger must be fired, false if we can prove the constraint will still |
1379 | | * be satisfied. |
1380 | | * |
1381 | | * newslot will be NULL if this is called for a delete. |
1382 | | */ |
1383 | | bool |
1384 | | RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, |
1385 | | TupleTableSlot *oldslot, TupleTableSlot *newslot) |
1386 | 0 | { |
1387 | 0 | const RI_ConstraintInfo *riinfo; |
1388 | |
|
1389 | 0 | riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true); |
1390 | | |
1391 | | /* |
1392 | | * If any old key value is NULL, the row could not have been referenced by |
1393 | | * an FK row, so no check is needed. |
1394 | | */ |
1395 | 0 | if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL) |
1396 | 0 | return false; |
1397 | | |
1398 | | /* If all old and new key values are equal, no check is needed */ |
1399 | 0 | if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true)) |
1400 | 0 | return false; |
1401 | | |
1402 | | /* Else we need to fire the trigger. */ |
1403 | 0 | return true; |
1404 | 0 | } |
1405 | | |
1406 | | /* |
1407 | | * RI_FKey_fk_upd_check_required - |
1408 | | * |
1409 | | * Check if we really need to fire the RI trigger for an update to an FK |
1410 | | * relation. This is called by the AFTER trigger queue manager to see if |
1411 | | * it can skip queuing an instance of an RI trigger. Returns true if the |
1412 | | * trigger must be fired, false if we can prove the constraint will still |
1413 | | * be satisfied. |
1414 | | */ |
1415 | | bool |
1416 | | RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, |
1417 | | TupleTableSlot *oldslot, TupleTableSlot *newslot) |
1418 | 0 | { |
1419 | 0 | const RI_ConstraintInfo *riinfo; |
1420 | 0 | int ri_nullcheck; |
1421 | | |
1422 | | /* |
1423 | | * AfterTriggerSaveEvent() handles things such that this function is never |
1424 | | * called for partitioned tables. |
1425 | | */ |
1426 | 0 | Assert(fk_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE); |
1427 | |
|
1428 | 0 | riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); |
1429 | |
|
1430 | 0 | ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false); |
1431 | | |
1432 | | /* |
1433 | | * If all new key values are NULL, the row satisfies the constraint, so no |
1434 | | * check is needed. |
1435 | | */ |
1436 | 0 | if (ri_nullcheck == RI_KEYS_ALL_NULL) |
1437 | 0 | return false; |
1438 | | |
1439 | | /* |
1440 | | * If some new key values are NULL, the behavior depends on the match |
1441 | | * type. |
1442 | | */ |
1443 | 0 | else if (ri_nullcheck == RI_KEYS_SOME_NULL) |
1444 | 0 | { |
1445 | 0 | switch (riinfo->confmatchtype) |
1446 | 0 | { |
1447 | 0 | case FKCONSTR_MATCH_SIMPLE: |
1448 | | |
1449 | | /* |
1450 | | * If any new key value is NULL, the row must satisfy the |
1451 | | * constraint, so no check is needed. |
1452 | | */ |
1453 | 0 | return false; |
1454 | | |
1455 | 0 | case FKCONSTR_MATCH_PARTIAL: |
1456 | | |
1457 | | /* |
1458 | | * Don't know, must run full check. |
1459 | | */ |
1460 | 0 | break; |
1461 | | |
1462 | 0 | case FKCONSTR_MATCH_FULL: |
1463 | | |
1464 | | /* |
1465 | | * If some new key values are NULL, the row fails the |
1466 | | * constraint. We must not throw error here, because the row |
1467 | | * might get invalidated before the constraint is to be |
1468 | | * checked, but we should queue the event to apply the check |
1469 | | * later. |
1470 | | */ |
1471 | 0 | return true; |
1472 | 0 | } |
1473 | 0 | } |
1474 | | |
1475 | | /* |
1476 | | * Continues here for no new key values are NULL, or we couldn't decide |
1477 | | * yet. |
1478 | | */ |
1479 | | |
1480 | | /* |
1481 | | * If the original row was inserted by our own transaction, we must fire |
1482 | | * the trigger whether or not the keys are equal. This is because our |
1483 | | * UPDATE will invalidate the INSERT so that the INSERT RI trigger will |
1484 | | * not do anything; so we had better do the UPDATE check. (We could skip |
1485 | | * this if we knew the INSERT trigger already fired, but there is no easy |
1486 | | * way to know that.) |
1487 | | */ |
1488 | 0 | if (slot_is_current_xact_tuple(oldslot)) |
1489 | 0 | return true; |
1490 | | |
1491 | | /* If all old and new key values are equal, no check is needed */ |
1492 | 0 | if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false)) |
1493 | 0 | return false; |
1494 | | |
1495 | | /* Else we need to fire the trigger. */ |
1496 | 0 | return true; |
1497 | 0 | } |
1498 | | |
1499 | | /* |
1500 | | * RI_Initial_Check - |
1501 | | * |
1502 | | * Check an entire table for non-matching values using a single query. |
1503 | | * This is not a trigger procedure, but is called during ALTER TABLE |
1504 | | * ADD FOREIGN KEY to validate the initial table contents. |
1505 | | * |
1506 | | * We expect that the caller has made provision to prevent any problems |
1507 | | * caused by concurrent actions. This could be either by locking rel and |
1508 | | * pkrel at ShareRowExclusiveLock or higher, or by otherwise ensuring |
1509 | | * that triggers implementing the checks are already active. |
1510 | | * Hence, we do not need to lock individual rows for the check. |
1511 | | * |
1512 | | * If the check fails because the current user doesn't have permissions |
1513 | | * to read both tables, return false to let our caller know that they will |
1514 | | * need to do something else to check the constraint. |
1515 | | */ |
1516 | | bool |
1517 | | RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) |
1518 | 0 | { |
1519 | 0 | const RI_ConstraintInfo *riinfo; |
1520 | 0 | StringInfoData querybuf; |
1521 | 0 | char pkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1522 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1523 | 0 | char pkattname[MAX_QUOTED_NAME_LEN + 3]; |
1524 | 0 | char fkattname[MAX_QUOTED_NAME_LEN + 3]; |
1525 | 0 | RangeTblEntry *rte; |
1526 | 0 | RTEPermissionInfo *pk_perminfo; |
1527 | 0 | RTEPermissionInfo *fk_perminfo; |
1528 | 0 | List *rtes = NIL; |
1529 | 0 | List *perminfos = NIL; |
1530 | 0 | const char *sep; |
1531 | 0 | const char *fk_only; |
1532 | 0 | const char *pk_only; |
1533 | 0 | int save_nestlevel; |
1534 | 0 | char workmembuf[32]; |
1535 | 0 | int spi_result; |
1536 | 0 | SPIPlanPtr qplan; |
1537 | |
|
1538 | 0 | riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); |
1539 | | |
1540 | | /* |
1541 | | * Check to make sure current user has enough permissions to do the test |
1542 | | * query. (If not, caller can fall back to the trigger method, which |
1543 | | * works because it changes user IDs on the fly.) |
1544 | | * |
1545 | | * XXX are there any other show-stopper conditions to check? |
1546 | | */ |
1547 | 0 | pk_perminfo = makeNode(RTEPermissionInfo); |
1548 | 0 | pk_perminfo->relid = RelationGetRelid(pk_rel); |
1549 | 0 | pk_perminfo->requiredPerms = ACL_SELECT; |
1550 | 0 | perminfos = lappend(perminfos, pk_perminfo); |
1551 | 0 | rte = makeNode(RangeTblEntry); |
1552 | 0 | rte->rtekind = RTE_RELATION; |
1553 | 0 | rte->relid = RelationGetRelid(pk_rel); |
1554 | 0 | rte->relkind = pk_rel->rd_rel->relkind; |
1555 | 0 | rte->rellockmode = AccessShareLock; |
1556 | 0 | rte->perminfoindex = list_length(perminfos); |
1557 | 0 | rtes = lappend(rtes, rte); |
1558 | |
|
1559 | 0 | fk_perminfo = makeNode(RTEPermissionInfo); |
1560 | 0 | fk_perminfo->relid = RelationGetRelid(fk_rel); |
1561 | 0 | fk_perminfo->requiredPerms = ACL_SELECT; |
1562 | 0 | perminfos = lappend(perminfos, fk_perminfo); |
1563 | 0 | rte = makeNode(RangeTblEntry); |
1564 | 0 | rte->rtekind = RTE_RELATION; |
1565 | 0 | rte->relid = RelationGetRelid(fk_rel); |
1566 | 0 | rte->relkind = fk_rel->rd_rel->relkind; |
1567 | 0 | rte->rellockmode = AccessShareLock; |
1568 | 0 | rte->perminfoindex = list_length(perminfos); |
1569 | 0 | rtes = lappend(rtes, rte); |
1570 | |
|
1571 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
1572 | 0 | { |
1573 | 0 | int attno; |
1574 | |
|
1575 | 0 | attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber; |
1576 | 0 | pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno); |
1577 | |
|
1578 | 0 | attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber; |
1579 | 0 | fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno); |
1580 | 0 | } |
1581 | |
|
1582 | 0 | if (!ExecCheckPermissions(rtes, perminfos, false)) |
1583 | 0 | return false; |
1584 | | |
1585 | | /* |
1586 | | * Also punt if RLS is enabled on either table unless this role has the |
1587 | | * bypassrls right or is the table owner of the table(s) involved which |
1588 | | * have RLS enabled. |
1589 | | */ |
1590 | 0 | if (!has_bypassrls_privilege(GetUserId()) && |
1591 | 0 | ((pk_rel->rd_rel->relrowsecurity && |
1592 | 0 | !object_ownercheck(RelationRelationId, RelationGetRelid(pk_rel), |
1593 | 0 | GetUserId())) || |
1594 | 0 | (fk_rel->rd_rel->relrowsecurity && |
1595 | 0 | !object_ownercheck(RelationRelationId, RelationGetRelid(fk_rel), |
1596 | 0 | GetUserId())))) |
1597 | 0 | return false; |
1598 | | |
1599 | | /*---------- |
1600 | | * The query string built is: |
1601 | | * SELECT fk.keycols FROM [ONLY] relname fk |
1602 | | * LEFT OUTER JOIN [ONLY] pkrelname pk |
1603 | | * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) |
1604 | | * WHERE pk.pkkeycol1 IS NULL AND |
1605 | | * For MATCH SIMPLE: |
1606 | | * (fk.keycol1 IS NOT NULL [AND ...]) |
1607 | | * For MATCH FULL: |
1608 | | * (fk.keycol1 IS NOT NULL [OR ...]) |
1609 | | * |
1610 | | * We attach COLLATE clauses to the operators when comparing columns |
1611 | | * that have different collations. |
1612 | | *---------- |
1613 | | */ |
1614 | 0 | initStringInfo(&querybuf); |
1615 | 0 | appendStringInfoString(&querybuf, "SELECT "); |
1616 | 0 | sep = ""; |
1617 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
1618 | 0 | { |
1619 | 0 | quoteOneName(fkattname, |
1620 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1621 | 0 | appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); |
1622 | 0 | sep = ", "; |
1623 | 0 | } |
1624 | |
|
1625 | 0 | quoteRelationName(pkrelname, pk_rel); |
1626 | 0 | quoteRelationName(fkrelname, fk_rel); |
1627 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
1628 | 0 | "" : "ONLY "; |
1629 | 0 | pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
1630 | 0 | "" : "ONLY "; |
1631 | 0 | appendStringInfo(&querybuf, |
1632 | 0 | " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON", |
1633 | 0 | fk_only, fkrelname, pk_only, pkrelname); |
1634 | |
|
1635 | 0 | strcpy(pkattname, "pk."); |
1636 | 0 | strcpy(fkattname, "fk."); |
1637 | 0 | sep = "("; |
1638 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
1639 | 0 | { |
1640 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
1641 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
1642 | 0 | Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); |
1643 | 0 | Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); |
1644 | |
|
1645 | 0 | quoteOneName(pkattname + 3, |
1646 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[i])); |
1647 | 0 | quoteOneName(fkattname + 3, |
1648 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1649 | 0 | ri_GenerateQual(&querybuf, sep, |
1650 | 0 | pkattname, pk_type, |
1651 | 0 | riinfo->pf_eq_oprs[i], |
1652 | 0 | fkattname, fk_type); |
1653 | 0 | if (pk_coll != fk_coll) |
1654 | 0 | ri_GenerateQualCollation(&querybuf, pk_coll); |
1655 | 0 | sep = "AND"; |
1656 | 0 | } |
1657 | | |
1658 | | /* |
1659 | | * It's sufficient to test any one pk attribute for null to detect a join |
1660 | | * failure. |
1661 | | */ |
1662 | 0 | quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0])); |
1663 | 0 | appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname); |
1664 | |
|
1665 | 0 | sep = ""; |
1666 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
1667 | 0 | { |
1668 | 0 | quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1669 | 0 | appendStringInfo(&querybuf, |
1670 | 0 | "%sfk.%s IS NOT NULL", |
1671 | 0 | sep, fkattname); |
1672 | 0 | switch (riinfo->confmatchtype) |
1673 | 0 | { |
1674 | 0 | case FKCONSTR_MATCH_SIMPLE: |
1675 | 0 | sep = " AND "; |
1676 | 0 | break; |
1677 | 0 | case FKCONSTR_MATCH_FULL: |
1678 | 0 | sep = " OR "; |
1679 | 0 | break; |
1680 | 0 | } |
1681 | 0 | } |
1682 | 0 | appendStringInfoChar(&querybuf, ')'); |
1683 | | |
1684 | | /* |
1685 | | * Temporarily increase work_mem so that the check query can be executed |
1686 | | * more efficiently. It seems okay to do this because the query is simple |
1687 | | * enough to not use a multiple of work_mem, and one typically would not |
1688 | | * have many large foreign-key validations happening concurrently. So |
1689 | | * this seems to meet the criteria for being considered a "maintenance" |
1690 | | * operation, and accordingly we use maintenance_work_mem. However, we |
1691 | | * must also set hash_mem_multiplier to 1, since it is surely not okay to |
1692 | | * let that get applied to the maintenance_work_mem value. |
1693 | | * |
1694 | | * We use the equivalent of a function SET option to allow the setting to |
1695 | | * persist for exactly the duration of the check query. guc.c also takes |
1696 | | * care of undoing the setting on error. |
1697 | | */ |
1698 | 0 | save_nestlevel = NewGUCNestLevel(); |
1699 | |
|
1700 | 0 | snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); |
1701 | 0 | (void) set_config_option("work_mem", workmembuf, |
1702 | 0 | PGC_USERSET, PGC_S_SESSION, |
1703 | 0 | GUC_ACTION_SAVE, true, 0, false); |
1704 | 0 | (void) set_config_option("hash_mem_multiplier", "1", |
1705 | 0 | PGC_USERSET, PGC_S_SESSION, |
1706 | 0 | GUC_ACTION_SAVE, true, 0, false); |
1707 | |
|
1708 | 0 | SPI_connect(); |
1709 | | |
1710 | | /* |
1711 | | * Generate the plan. We don't need to cache it, and there are no |
1712 | | * arguments to the plan. |
1713 | | */ |
1714 | 0 | qplan = SPI_prepare(querybuf.data, 0, NULL); |
1715 | |
|
1716 | 0 | if (qplan == NULL) |
1717 | 0 | elog(ERROR, "SPI_prepare returned %s for %s", |
1718 | 0 | SPI_result_code_string(SPI_result), querybuf.data); |
1719 | | |
1720 | | /* |
1721 | | * Run the plan. For safety we force a current snapshot to be used. (In |
1722 | | * transaction-snapshot mode, this arguably violates transaction isolation |
1723 | | * rules, but we really haven't got much choice.) We don't need to |
1724 | | * register the snapshot, because SPI_execute_snapshot will see to it. We |
1725 | | * need at most one tuple returned, so pass limit = 1. |
1726 | | */ |
1727 | 0 | spi_result = SPI_execute_snapshot(qplan, |
1728 | 0 | NULL, NULL, |
1729 | 0 | GetLatestSnapshot(), |
1730 | 0 | InvalidSnapshot, |
1731 | 0 | true, false, 1); |
1732 | | |
1733 | | /* Check result */ |
1734 | 0 | if (spi_result != SPI_OK_SELECT) |
1735 | 0 | elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); |
1736 | | |
1737 | | /* Did we find a tuple violating the constraint? */ |
1738 | 0 | if (SPI_processed > 0) |
1739 | 0 | { |
1740 | 0 | TupleTableSlot *slot; |
1741 | 0 | HeapTuple tuple = SPI_tuptable->vals[0]; |
1742 | 0 | TupleDesc tupdesc = SPI_tuptable->tupdesc; |
1743 | 0 | RI_ConstraintInfo fake_riinfo; |
1744 | |
|
1745 | 0 | slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); |
1746 | |
|
1747 | 0 | heap_deform_tuple(tuple, tupdesc, |
1748 | 0 | slot->tts_values, slot->tts_isnull); |
1749 | 0 | ExecStoreVirtualTuple(slot); |
1750 | | |
1751 | | /* |
1752 | | * The columns to look at in the result tuple are 1..N, not whatever |
1753 | | * they are in the fk_rel. Hack up riinfo so that the subroutines |
1754 | | * called here will behave properly. |
1755 | | * |
1756 | | * In addition to this, we have to pass the correct tupdesc to |
1757 | | * ri_ReportViolation, overriding its normal habit of using the pk_rel |
1758 | | * or fk_rel's tupdesc. |
1759 | | */ |
1760 | 0 | memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo)); |
1761 | 0 | for (int i = 0; i < fake_riinfo.nkeys; i++) |
1762 | 0 | fake_riinfo.fk_attnums[i] = i + 1; |
1763 | | |
1764 | | /* |
1765 | | * If it's MATCH FULL, and there are any nulls in the FK keys, |
1766 | | * complain about that rather than the lack of a match. MATCH FULL |
1767 | | * disallows partially-null FK rows. |
1768 | | */ |
1769 | 0 | if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && |
1770 | 0 | ri_NullCheck(tupdesc, slot, &fake_riinfo, false) != RI_KEYS_NONE_NULL) |
1771 | 0 | ereport(ERROR, |
1772 | 0 | (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), |
1773 | 0 | errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", |
1774 | 0 | RelationGetRelationName(fk_rel), |
1775 | 0 | NameStr(fake_riinfo.conname)), |
1776 | 0 | errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), |
1777 | 0 | errtableconstraint(fk_rel, |
1778 | 0 | NameStr(fake_riinfo.conname)))); |
1779 | | |
1780 | | /* |
1781 | | * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK |
1782 | | * query, which isn't true, but will cause it to use |
1783 | | * fake_riinfo.fk_attnums as we need. |
1784 | | */ |
1785 | 0 | ri_ReportViolation(&fake_riinfo, |
1786 | 0 | pk_rel, fk_rel, |
1787 | 0 | slot, tupdesc, |
1788 | 0 | RI_PLAN_CHECK_LOOKUPPK, false, false); |
1789 | |
|
1790 | 0 | ExecDropSingleTupleTableSlot(slot); |
1791 | 0 | } |
1792 | | |
1793 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
1794 | 0 | elog(ERROR, "SPI_finish failed"); |
1795 | | |
1796 | | /* |
1797 | | * Restore work_mem and hash_mem_multiplier. |
1798 | | */ |
1799 | 0 | AtEOXact_GUC(true, save_nestlevel); |
1800 | |
|
1801 | 0 | return true; |
1802 | 0 | } |
1803 | | |
1804 | | /* |
1805 | | * RI_PartitionRemove_Check - |
1806 | | * |
1807 | | * Verify no referencing values exist, when a partition is detached on |
1808 | | * the referenced side of a foreign key constraint. |
1809 | | */ |
1810 | | void |
1811 | | RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) |
1812 | 0 | { |
1813 | 0 | const RI_ConstraintInfo *riinfo; |
1814 | 0 | StringInfoData querybuf; |
1815 | 0 | char *constraintDef; |
1816 | 0 | char pkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1817 | 0 | char fkrelname[MAX_QUOTED_REL_NAME_LEN]; |
1818 | 0 | char pkattname[MAX_QUOTED_NAME_LEN + 3]; |
1819 | 0 | char fkattname[MAX_QUOTED_NAME_LEN + 3]; |
1820 | 0 | const char *sep; |
1821 | 0 | const char *fk_only; |
1822 | 0 | int save_nestlevel; |
1823 | 0 | char workmembuf[32]; |
1824 | 0 | int spi_result; |
1825 | 0 | SPIPlanPtr qplan; |
1826 | 0 | int i; |
1827 | |
|
1828 | 0 | riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false); |
1829 | | |
1830 | | /* |
1831 | | * We don't check permissions before displaying the error message, on the |
1832 | | * assumption that the user detaching the partition must have enough |
1833 | | * privileges to examine the table contents anyhow. |
1834 | | */ |
1835 | | |
1836 | | /*---------- |
1837 | | * The query string built is: |
1838 | | * SELECT fk.keycols FROM [ONLY] relname fk |
1839 | | * JOIN pkrelname pk |
1840 | | * ON (pk.pkkeycol1=fk.keycol1 [AND ...]) |
1841 | | * WHERE (<partition constraint>) AND |
1842 | | * For MATCH SIMPLE: |
1843 | | * (fk.keycol1 IS NOT NULL [AND ...]) |
1844 | | * For MATCH FULL: |
1845 | | * (fk.keycol1 IS NOT NULL [OR ...]) |
1846 | | * |
1847 | | * We attach COLLATE clauses to the operators when comparing columns |
1848 | | * that have different collations. |
1849 | | *---------- |
1850 | | */ |
1851 | 0 | initStringInfo(&querybuf); |
1852 | 0 | appendStringInfoString(&querybuf, "SELECT "); |
1853 | 0 | sep = ""; |
1854 | 0 | for (i = 0; i < riinfo->nkeys; i++) |
1855 | 0 | { |
1856 | 0 | quoteOneName(fkattname, |
1857 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1858 | 0 | appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname); |
1859 | 0 | sep = ", "; |
1860 | 0 | } |
1861 | |
|
1862 | 0 | quoteRelationName(pkrelname, pk_rel); |
1863 | 0 | quoteRelationName(fkrelname, fk_rel); |
1864 | 0 | fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ? |
1865 | 0 | "" : "ONLY "; |
1866 | 0 | appendStringInfo(&querybuf, |
1867 | 0 | " FROM %s%s fk JOIN %s pk ON", |
1868 | 0 | fk_only, fkrelname, pkrelname); |
1869 | 0 | strcpy(pkattname, "pk."); |
1870 | 0 | strcpy(fkattname, "fk."); |
1871 | 0 | sep = "("; |
1872 | 0 | for (i = 0; i < riinfo->nkeys; i++) |
1873 | 0 | { |
1874 | 0 | Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]); |
1875 | 0 | Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]); |
1876 | 0 | Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]); |
1877 | 0 | Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]); |
1878 | |
|
1879 | 0 | quoteOneName(pkattname + 3, |
1880 | 0 | RIAttName(pk_rel, riinfo->pk_attnums[i])); |
1881 | 0 | quoteOneName(fkattname + 3, |
1882 | 0 | RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1883 | 0 | ri_GenerateQual(&querybuf, sep, |
1884 | 0 | pkattname, pk_type, |
1885 | 0 | riinfo->pf_eq_oprs[i], |
1886 | 0 | fkattname, fk_type); |
1887 | 0 | if (pk_coll != fk_coll) |
1888 | 0 | ri_GenerateQualCollation(&querybuf, pk_coll); |
1889 | 0 | sep = "AND"; |
1890 | 0 | } |
1891 | | |
1892 | | /* |
1893 | | * Start the WHERE clause with the partition constraint (except if this is |
1894 | | * the default partition and there's no other partition, because the |
1895 | | * partition constraint is the empty string in that case.) |
1896 | | */ |
1897 | 0 | constraintDef = pg_get_partconstrdef_string(RelationGetRelid(pk_rel), "pk"); |
1898 | 0 | if (constraintDef && constraintDef[0] != '\0') |
1899 | 0 | appendStringInfo(&querybuf, ") WHERE %s AND (", |
1900 | 0 | constraintDef); |
1901 | 0 | else |
1902 | 0 | appendStringInfoString(&querybuf, ") WHERE ("); |
1903 | |
|
1904 | 0 | sep = ""; |
1905 | 0 | for (i = 0; i < riinfo->nkeys; i++) |
1906 | 0 | { |
1907 | 0 | quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i])); |
1908 | 0 | appendStringInfo(&querybuf, |
1909 | 0 | "%sfk.%s IS NOT NULL", |
1910 | 0 | sep, fkattname); |
1911 | 0 | switch (riinfo->confmatchtype) |
1912 | 0 | { |
1913 | 0 | case FKCONSTR_MATCH_SIMPLE: |
1914 | 0 | sep = " AND "; |
1915 | 0 | break; |
1916 | 0 | case FKCONSTR_MATCH_FULL: |
1917 | 0 | sep = " OR "; |
1918 | 0 | break; |
1919 | 0 | } |
1920 | 0 | } |
1921 | 0 | appendStringInfoChar(&querybuf, ')'); |
1922 | | |
1923 | | /* |
1924 | | * Temporarily increase work_mem so that the check query can be executed |
1925 | | * more efficiently. It seems okay to do this because the query is simple |
1926 | | * enough to not use a multiple of work_mem, and one typically would not |
1927 | | * have many large foreign-key validations happening concurrently. So |
1928 | | * this seems to meet the criteria for being considered a "maintenance" |
1929 | | * operation, and accordingly we use maintenance_work_mem. However, we |
1930 | | * must also set hash_mem_multiplier to 1, since it is surely not okay to |
1931 | | * let that get applied to the maintenance_work_mem value. |
1932 | | * |
1933 | | * We use the equivalent of a function SET option to allow the setting to |
1934 | | * persist for exactly the duration of the check query. guc.c also takes |
1935 | | * care of undoing the setting on error. |
1936 | | */ |
1937 | 0 | save_nestlevel = NewGUCNestLevel(); |
1938 | |
|
1939 | 0 | snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem); |
1940 | 0 | (void) set_config_option("work_mem", workmembuf, |
1941 | 0 | PGC_USERSET, PGC_S_SESSION, |
1942 | 0 | GUC_ACTION_SAVE, true, 0, false); |
1943 | 0 | (void) set_config_option("hash_mem_multiplier", "1", |
1944 | 0 | PGC_USERSET, PGC_S_SESSION, |
1945 | 0 | GUC_ACTION_SAVE, true, 0, false); |
1946 | |
|
1947 | 0 | SPI_connect(); |
1948 | | |
1949 | | /* |
1950 | | * Generate the plan. We don't need to cache it, and there are no |
1951 | | * arguments to the plan. |
1952 | | */ |
1953 | 0 | qplan = SPI_prepare(querybuf.data, 0, NULL); |
1954 | |
|
1955 | 0 | if (qplan == NULL) |
1956 | 0 | elog(ERROR, "SPI_prepare returned %s for %s", |
1957 | 0 | SPI_result_code_string(SPI_result), querybuf.data); |
1958 | | |
1959 | | /* |
1960 | | * Run the plan. For safety we force a current snapshot to be used. (In |
1961 | | * transaction-snapshot mode, this arguably violates transaction isolation |
1962 | | * rules, but we really haven't got much choice.) We don't need to |
1963 | | * register the snapshot, because SPI_execute_snapshot will see to it. We |
1964 | | * need at most one tuple returned, so pass limit = 1. |
1965 | | */ |
1966 | 0 | spi_result = SPI_execute_snapshot(qplan, |
1967 | 0 | NULL, NULL, |
1968 | 0 | GetLatestSnapshot(), |
1969 | 0 | InvalidSnapshot, |
1970 | 0 | true, false, 1); |
1971 | | |
1972 | | /* Check result */ |
1973 | 0 | if (spi_result != SPI_OK_SELECT) |
1974 | 0 | elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); |
1975 | | |
1976 | | /* Did we find a tuple that would violate the constraint? */ |
1977 | 0 | if (SPI_processed > 0) |
1978 | 0 | { |
1979 | 0 | TupleTableSlot *slot; |
1980 | 0 | HeapTuple tuple = SPI_tuptable->vals[0]; |
1981 | 0 | TupleDesc tupdesc = SPI_tuptable->tupdesc; |
1982 | 0 | RI_ConstraintInfo fake_riinfo; |
1983 | |
|
1984 | 0 | slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); |
1985 | |
|
1986 | 0 | heap_deform_tuple(tuple, tupdesc, |
1987 | 0 | slot->tts_values, slot->tts_isnull); |
1988 | 0 | ExecStoreVirtualTuple(slot); |
1989 | | |
1990 | | /* |
1991 | | * The columns to look at in the result tuple are 1..N, not whatever |
1992 | | * they are in the fk_rel. Hack up riinfo so that ri_ReportViolation |
1993 | | * will behave properly. |
1994 | | * |
1995 | | * In addition to this, we have to pass the correct tupdesc to |
1996 | | * ri_ReportViolation, overriding its normal habit of using the pk_rel |
1997 | | * or fk_rel's tupdesc. |
1998 | | */ |
1999 | 0 | memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo)); |
2000 | 0 | for (i = 0; i < fake_riinfo.nkeys; i++) |
2001 | 0 | fake_riinfo.pk_attnums[i] = i + 1; |
2002 | |
|
2003 | 0 | ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel, |
2004 | 0 | slot, tupdesc, 0, false, true); |
2005 | 0 | } |
2006 | |
|
2007 | 0 | if (SPI_finish() != SPI_OK_FINISH) |
2008 | 0 | elog(ERROR, "SPI_finish failed"); |
2009 | | |
2010 | | /* |
2011 | | * Restore work_mem and hash_mem_multiplier. |
2012 | | */ |
2013 | 0 | AtEOXact_GUC(true, save_nestlevel); |
2014 | 0 | } |
2015 | | |
2016 | | |
2017 | | /* ---------- |
2018 | | * Local functions below |
2019 | | * ---------- |
2020 | | */ |
2021 | | |
2022 | | |
2023 | | /* |
2024 | | * quoteOneName --- safely quote a single SQL name |
2025 | | * |
2026 | | * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0) |
2027 | | */ |
2028 | | static void |
2029 | | quoteOneName(char *buffer, const char *name) |
2030 | 0 | { |
2031 | | /* Rather than trying to be smart, just always quote it. */ |
2032 | 0 | *buffer++ = '"'; |
2033 | 0 | while (*name) |
2034 | 0 | { |
2035 | 0 | if (*name == '"') |
2036 | 0 | *buffer++ = '"'; |
2037 | 0 | *buffer++ = *name++; |
2038 | 0 | } |
2039 | 0 | *buffer++ = '"'; |
2040 | 0 | *buffer = '\0'; |
2041 | 0 | } |
2042 | | |
2043 | | /* |
2044 | | * quoteRelationName --- safely quote a fully qualified relation name |
2045 | | * |
2046 | | * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0) |
2047 | | */ |
2048 | | static void |
2049 | | quoteRelationName(char *buffer, Relation rel) |
2050 | 0 | { |
2051 | 0 | quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel))); |
2052 | 0 | buffer += strlen(buffer); |
2053 | 0 | *buffer++ = '.'; |
2054 | 0 | quoteOneName(buffer, RelationGetRelationName(rel)); |
2055 | 0 | } |
2056 | | |
2057 | | /* |
2058 | | * ri_GenerateQual --- generate a WHERE clause equating two variables |
2059 | | * |
2060 | | * This basically appends " sep leftop op rightop" to buf, adding casts |
2061 | | * and schema qualification as needed to ensure that the parser will select |
2062 | | * the operator we specify. leftop and rightop should be parenthesized |
2063 | | * if they aren't variables or parameters. |
2064 | | */ |
2065 | | static void |
2066 | | ri_GenerateQual(StringInfo buf, |
2067 | | const char *sep, |
2068 | | const char *leftop, Oid leftoptype, |
2069 | | Oid opoid, |
2070 | | const char *rightop, Oid rightoptype) |
2071 | 0 | { |
2072 | 0 | appendStringInfo(buf, " %s ", sep); |
2073 | 0 | generate_operator_clause(buf, leftop, leftoptype, opoid, |
2074 | 0 | rightop, rightoptype); |
2075 | 0 | } |
2076 | | |
2077 | | /* |
2078 | | * ri_GenerateQualCollation --- add a COLLATE spec to a WHERE clause |
2079 | | * |
2080 | | * We only have to use this function when directly comparing the referencing |
2081 | | * and referenced columns, if they are of different collations; else the |
2082 | | * parser will fail to resolve the collation to use. We don't need to use |
2083 | | * this function for RI queries that compare a variable to a $n parameter. |
2084 | | * Since parameter symbols always have default collation, the effect will be |
2085 | | * to use the variable's collation. |
2086 | | * |
2087 | | * Note that we require that the collations of the referencing and the |
2088 | | * referenced column have the same notion of equality: Either they have to |
2089 | | * both be deterministic or else they both have to be the same. (See also |
2090 | | * ATAddForeignKeyConstraint().) |
2091 | | */ |
2092 | | static void |
2093 | | ri_GenerateQualCollation(StringInfo buf, Oid collation) |
2094 | 0 | { |
2095 | 0 | HeapTuple tp; |
2096 | 0 | Form_pg_collation colltup; |
2097 | 0 | char *collname; |
2098 | 0 | char onename[MAX_QUOTED_NAME_LEN]; |
2099 | | |
2100 | | /* Nothing to do if it's a noncollatable data type */ |
2101 | 0 | if (!OidIsValid(collation)) |
2102 | 0 | return; |
2103 | | |
2104 | 0 | tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation)); |
2105 | 0 | if (!HeapTupleIsValid(tp)) |
2106 | 0 | elog(ERROR, "cache lookup failed for collation %u", collation); |
2107 | 0 | colltup = (Form_pg_collation) GETSTRUCT(tp); |
2108 | 0 | collname = NameStr(colltup->collname); |
2109 | | |
2110 | | /* |
2111 | | * We qualify the name always, for simplicity and to ensure the query is |
2112 | | * not search-path-dependent. |
2113 | | */ |
2114 | 0 | quoteOneName(onename, get_namespace_name(colltup->collnamespace)); |
2115 | 0 | appendStringInfo(buf, " COLLATE %s", onename); |
2116 | 0 | quoteOneName(onename, collname); |
2117 | 0 | appendStringInfo(buf, ".%s", onename); |
2118 | |
|
2119 | 0 | ReleaseSysCache(tp); |
2120 | 0 | } |
2121 | | |
2122 | | /* ---------- |
2123 | | * ri_BuildQueryKey - |
2124 | | * |
2125 | | * Construct a hashtable key for a prepared SPI plan of an FK constraint. |
2126 | | * |
2127 | | * key: output argument, *key is filled in based on the other arguments |
2128 | | * riinfo: info derived from pg_constraint entry |
2129 | | * constr_queryno: an internal number identifying the query type |
2130 | | * (see RI_PLAN_XXX constants at head of file) |
2131 | | * ---------- |
2132 | | */ |
2133 | | static void |
2134 | | ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, |
2135 | | int32 constr_queryno) |
2136 | 0 | { |
2137 | | /* |
2138 | | * Inherited constraints with a common ancestor can share ri_query_cache |
2139 | | * entries for all query types except RI_PLAN_CHECK_LOOKUPPK_FROM_PK. |
2140 | | * Except in that case, the query processes the other table involved in |
2141 | | * the FK constraint (i.e., not the table on which the trigger has been |
2142 | | * fired), and so it will be the same for all members of the inheritance |
2143 | | * tree. So we may use the root constraint's OID in the hash key, rather |
2144 | | * than the constraint's own OID. This avoids creating duplicate SPI |
2145 | | * plans, saving lots of work and memory when there are many partitions |
2146 | | * with similar FK constraints. |
2147 | | * |
2148 | | * (Note that we must still have a separate RI_ConstraintInfo for each |
2149 | | * constraint, because partitions can have different column orders, |
2150 | | * resulting in different pk_attnums[] or fk_attnums[] array contents.) |
2151 | | * |
2152 | | * We assume struct RI_QueryKey contains no padding bytes, else we'd need |
2153 | | * to use memset to clear them. |
2154 | | */ |
2155 | 0 | if (constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK) |
2156 | 0 | key->constr_id = riinfo->constraint_root_id; |
2157 | 0 | else |
2158 | 0 | key->constr_id = riinfo->constraint_id; |
2159 | 0 | key->constr_queryno = constr_queryno; |
2160 | 0 | } |
2161 | | |
2162 | | /* |
2163 | | * Check that RI trigger function was called in expected context |
2164 | | */ |
2165 | | static void |
2166 | | ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind) |
2167 | 0 | { |
2168 | 0 | TriggerData *trigdata = (TriggerData *) fcinfo->context; |
2169 | |
|
2170 | 0 | if (!CALLED_AS_TRIGGER(fcinfo)) |
2171 | 0 | ereport(ERROR, |
2172 | 0 | (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
2173 | 0 | errmsg("function \"%s\" was not called by trigger manager", funcname))); |
2174 | | |
2175 | | /* |
2176 | | * Check proper event |
2177 | | */ |
2178 | 0 | if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) || |
2179 | 0 | !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event)) |
2180 | 0 | ereport(ERROR, |
2181 | 0 | (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
2182 | 0 | errmsg("function \"%s\" must be fired AFTER ROW", funcname))); |
2183 | | |
2184 | 0 | switch (tgkind) |
2185 | 0 | { |
2186 | 0 | case RI_TRIGTYPE_INSERT: |
2187 | 0 | if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event)) |
2188 | 0 | ereport(ERROR, |
2189 | 0 | (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
2190 | 0 | errmsg("function \"%s\" must be fired for INSERT", funcname))); |
2191 | 0 | break; |
2192 | 0 | case RI_TRIGTYPE_UPDATE: |
2193 | 0 | if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) |
2194 | 0 | ereport(ERROR, |
2195 | 0 | (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
2196 | 0 | errmsg("function \"%s\" must be fired for UPDATE", funcname))); |
2197 | 0 | break; |
2198 | 0 | case RI_TRIGTYPE_DELETE: |
2199 | 0 | if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) |
2200 | 0 | ereport(ERROR, |
2201 | 0 | (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), |
2202 | 0 | errmsg("function \"%s\" must be fired for DELETE", funcname))); |
2203 | 0 | break; |
2204 | 0 | } |
2205 | 0 | } |
2206 | | |
2207 | | |
2208 | | /* |
2209 | | * Fetch the RI_ConstraintInfo struct for the trigger's FK constraint. |
2210 | | */ |
2211 | | static const RI_ConstraintInfo * |
2212 | | ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk) |
2213 | 0 | { |
2214 | 0 | Oid constraintOid = trigger->tgconstraint; |
2215 | 0 | const RI_ConstraintInfo *riinfo; |
2216 | | |
2217 | | /* |
2218 | | * Check that the FK constraint's OID is available; it might not be if |
2219 | | * we've been invoked via an ordinary trigger or an old-style "constraint |
2220 | | * trigger". |
2221 | | */ |
2222 | 0 | if (!OidIsValid(constraintOid)) |
2223 | 0 | ereport(ERROR, |
2224 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
2225 | 0 | errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"", |
2226 | 0 | trigger->tgname, RelationGetRelationName(trig_rel)), |
2227 | 0 | errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT."))); |
2228 | | |
2229 | | /* Find or create a hashtable entry for the constraint */ |
2230 | 0 | riinfo = ri_LoadConstraintInfo(constraintOid); |
2231 | | |
2232 | | /* Do some easy cross-checks against the trigger call data */ |
2233 | 0 | if (rel_is_pk) |
2234 | 0 | { |
2235 | 0 | if (riinfo->fk_relid != trigger->tgconstrrelid || |
2236 | 0 | riinfo->pk_relid != RelationGetRelid(trig_rel)) |
2237 | 0 | elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", |
2238 | 0 | trigger->tgname, RelationGetRelationName(trig_rel)); |
2239 | 0 | } |
2240 | 0 | else |
2241 | 0 | { |
2242 | 0 | if (riinfo->fk_relid != RelationGetRelid(trig_rel) || |
2243 | 0 | riinfo->pk_relid != trigger->tgconstrrelid) |
2244 | 0 | elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"", |
2245 | 0 | trigger->tgname, RelationGetRelationName(trig_rel)); |
2246 | 0 | } |
2247 | | |
2248 | 0 | if (riinfo->confmatchtype != FKCONSTR_MATCH_FULL && |
2249 | 0 | riinfo->confmatchtype != FKCONSTR_MATCH_PARTIAL && |
2250 | 0 | riinfo->confmatchtype != FKCONSTR_MATCH_SIMPLE) |
2251 | 0 | elog(ERROR, "unrecognized confmatchtype: %d", |
2252 | 0 | riinfo->confmatchtype); |
2253 | | |
2254 | 0 | if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL) |
2255 | 0 | ereport(ERROR, |
2256 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
2257 | 0 | errmsg("MATCH PARTIAL not yet implemented"))); |
2258 | | |
2259 | 0 | return riinfo; |
2260 | 0 | } |
2261 | | |
2262 | | /* |
2263 | | * Fetch or create the RI_ConstraintInfo struct for an FK constraint. |
2264 | | */ |
2265 | | static const RI_ConstraintInfo * |
2266 | | ri_LoadConstraintInfo(Oid constraintOid) |
2267 | 0 | { |
2268 | 0 | RI_ConstraintInfo *riinfo; |
2269 | 0 | bool found; |
2270 | 0 | HeapTuple tup; |
2271 | 0 | Form_pg_constraint conForm; |
2272 | | |
2273 | | /* |
2274 | | * On the first call initialize the hashtable |
2275 | | */ |
2276 | 0 | if (!ri_constraint_cache) |
2277 | 0 | ri_InitHashTables(); |
2278 | | |
2279 | | /* |
2280 | | * Find or create a hash entry. If we find a valid one, just return it. |
2281 | | */ |
2282 | 0 | riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache, |
2283 | 0 | &constraintOid, |
2284 | 0 | HASH_ENTER, &found); |
2285 | 0 | if (!found) |
2286 | 0 | riinfo->valid = false; |
2287 | 0 | else if (riinfo->valid) |
2288 | 0 | return riinfo; |
2289 | | |
2290 | | /* |
2291 | | * Fetch the pg_constraint row so we can fill in the entry. |
2292 | | */ |
2293 | 0 | tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); |
2294 | 0 | if (!HeapTupleIsValid(tup)) /* should not happen */ |
2295 | 0 | elog(ERROR, "cache lookup failed for constraint %u", constraintOid); |
2296 | 0 | conForm = (Form_pg_constraint) GETSTRUCT(tup); |
2297 | |
|
2298 | 0 | if (conForm->contype != CONSTRAINT_FOREIGN) /* should not happen */ |
2299 | 0 | elog(ERROR, "constraint %u is not a foreign key constraint", |
2300 | 0 | constraintOid); |
2301 | | |
2302 | | /* And extract data */ |
2303 | 0 | Assert(riinfo->constraint_id == constraintOid); |
2304 | 0 | if (OidIsValid(conForm->conparentid)) |
2305 | 0 | riinfo->constraint_root_id = |
2306 | 0 | get_ri_constraint_root(conForm->conparentid); |
2307 | 0 | else |
2308 | 0 | riinfo->constraint_root_id = constraintOid; |
2309 | 0 | riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID, |
2310 | 0 | ObjectIdGetDatum(constraintOid)); |
2311 | 0 | riinfo->rootHashValue = GetSysCacheHashValue1(CONSTROID, |
2312 | 0 | ObjectIdGetDatum(riinfo->constraint_root_id)); |
2313 | 0 | memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData)); |
2314 | 0 | riinfo->pk_relid = conForm->confrelid; |
2315 | 0 | riinfo->fk_relid = conForm->conrelid; |
2316 | 0 | riinfo->confupdtype = conForm->confupdtype; |
2317 | 0 | riinfo->confdeltype = conForm->confdeltype; |
2318 | 0 | riinfo->confmatchtype = conForm->confmatchtype; |
2319 | 0 | riinfo->hasperiod = conForm->conperiod; |
2320 | |
|
2321 | 0 | DeconstructFkConstraintRow(tup, |
2322 | 0 | &riinfo->nkeys, |
2323 | 0 | riinfo->fk_attnums, |
2324 | 0 | riinfo->pk_attnums, |
2325 | 0 | riinfo->pf_eq_oprs, |
2326 | 0 | riinfo->pp_eq_oprs, |
2327 | 0 | riinfo->ff_eq_oprs, |
2328 | 0 | &riinfo->ndelsetcols, |
2329 | 0 | riinfo->confdelsetcols); |
2330 | | |
2331 | | /* |
2332 | | * For temporal FKs, get the operators and functions we need. We ask the |
2333 | | * opclass of the PK element for these. This all gets cached (as does the |
2334 | | * generated plan), so there's no performance issue. |
2335 | | */ |
2336 | 0 | if (riinfo->hasperiod) |
2337 | 0 | { |
2338 | 0 | Oid opclass = get_index_column_opclass(conForm->conindid, riinfo->nkeys); |
2339 | |
|
2340 | 0 | FindFKPeriodOpers(opclass, |
2341 | 0 | &riinfo->period_contained_by_oper, |
2342 | 0 | &riinfo->agged_period_contained_by_oper, |
2343 | 0 | &riinfo->period_intersect_oper); |
2344 | 0 | } |
2345 | |
|
2346 | 0 | ReleaseSysCache(tup); |
2347 | | |
2348 | | /* |
2349 | | * For efficient processing of invalidation messages below, we keep a |
2350 | | * doubly-linked count list of all currently valid entries. |
2351 | | */ |
2352 | 0 | dclist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link); |
2353 | |
|
2354 | 0 | riinfo->valid = true; |
2355 | |
|
2356 | 0 | return riinfo; |
2357 | 0 | } |
2358 | | |
2359 | | /* |
2360 | | * get_ri_constraint_root |
2361 | | * Returns the OID of the constraint's root parent |
2362 | | */ |
2363 | | static Oid |
2364 | | get_ri_constraint_root(Oid constrOid) |
2365 | 0 | { |
2366 | 0 | for (;;) |
2367 | 0 | { |
2368 | 0 | HeapTuple tuple; |
2369 | 0 | Oid constrParentOid; |
2370 | |
|
2371 | 0 | tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid)); |
2372 | 0 | if (!HeapTupleIsValid(tuple)) |
2373 | 0 | elog(ERROR, "cache lookup failed for constraint %u", constrOid); |
2374 | 0 | constrParentOid = ((Form_pg_constraint) GETSTRUCT(tuple))->conparentid; |
2375 | 0 | ReleaseSysCache(tuple); |
2376 | 0 | if (!OidIsValid(constrParentOid)) |
2377 | 0 | break; /* we reached the root constraint */ |
2378 | 0 | constrOid = constrParentOid; |
2379 | 0 | } |
2380 | 0 | return constrOid; |
2381 | 0 | } |
2382 | | |
2383 | | /* |
2384 | | * Callback for pg_constraint inval events |
2385 | | * |
2386 | | * While most syscache callbacks just flush all their entries, pg_constraint |
2387 | | * gets enough update traffic that it's probably worth being smarter. |
2388 | | * Invalidate any ri_constraint_cache entry associated with the syscache |
2389 | | * entry with the specified hash value, or all entries if hashvalue == 0. |
2390 | | * |
2391 | | * Note: at the time a cache invalidation message is processed there may be |
2392 | | * active references to the cache. Because of this we never remove entries |
2393 | | * from the cache, but only mark them invalid, which is harmless to active |
2394 | | * uses. (Any query using an entry should hold a lock sufficient to keep that |
2395 | | * data from changing under it --- but we may get cache flushes anyway.) |
2396 | | */ |
2397 | | static void |
2398 | | InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) |
2399 | 0 | { |
2400 | 0 | dlist_mutable_iter iter; |
2401 | |
|
2402 | 0 | Assert(ri_constraint_cache != NULL); |
2403 | | |
2404 | | /* |
2405 | | * If the list of currently valid entries gets excessively large, we mark |
2406 | | * them all invalid so we can empty the list. This arrangement avoids |
2407 | | * O(N^2) behavior in situations where a session touches many foreign keys |
2408 | | * and also does many ALTER TABLEs, such as a restore from pg_dump. |
2409 | | */ |
2410 | 0 | if (dclist_count(&ri_constraint_cache_valid_list) > 1000) |
2411 | 0 | hashvalue = 0; /* pretend it's a cache reset */ |
2412 | |
|
2413 | 0 | dclist_foreach_modify(iter, &ri_constraint_cache_valid_list) |
2414 | 0 | { |
2415 | 0 | RI_ConstraintInfo *riinfo = dclist_container(RI_ConstraintInfo, |
2416 | 0 | valid_link, iter.cur); |
2417 | | |
2418 | | /* |
2419 | | * We must invalidate not only entries directly matching the given |
2420 | | * hash value, but also child entries, in case the invalidation |
2421 | | * affects a root constraint. |
2422 | | */ |
2423 | 0 | if (hashvalue == 0 || |
2424 | 0 | riinfo->oidHashValue == hashvalue || |
2425 | 0 | riinfo->rootHashValue == hashvalue) |
2426 | 0 | { |
2427 | 0 | riinfo->valid = false; |
2428 | | /* Remove invalidated entries from the list, too */ |
2429 | 0 | dclist_delete_from(&ri_constraint_cache_valid_list, iter.cur); |
2430 | 0 | } |
2431 | 0 | } |
2432 | 0 | } |
2433 | | |
2434 | | |
2435 | | /* |
2436 | | * Prepare execution plan for a query to enforce an RI restriction |
2437 | | */ |
2438 | | static SPIPlanPtr |
2439 | | ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, |
2440 | | RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel) |
2441 | 0 | { |
2442 | 0 | SPIPlanPtr qplan; |
2443 | 0 | Relation query_rel; |
2444 | 0 | Oid save_userid; |
2445 | 0 | int save_sec_context; |
2446 | | |
2447 | | /* |
2448 | | * Use the query type code to determine whether the query is run against |
2449 | | * the PK or FK table; we'll do the check as that table's owner |
2450 | | */ |
2451 | 0 | if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) |
2452 | 0 | query_rel = pk_rel; |
2453 | 0 | else |
2454 | 0 | query_rel = fk_rel; |
2455 | | |
2456 | | /* Switch to proper UID to perform check as */ |
2457 | 0 | GetUserIdAndSecContext(&save_userid, &save_sec_context); |
2458 | 0 | SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, |
2459 | 0 | save_sec_context | SECURITY_LOCAL_USERID_CHANGE | |
2460 | 0 | SECURITY_NOFORCE_RLS); |
2461 | | |
2462 | | /* Create the plan */ |
2463 | 0 | qplan = SPI_prepare(querystr, nargs, argtypes); |
2464 | |
|
2465 | 0 | if (qplan == NULL) |
2466 | 0 | elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), querystr); |
2467 | | |
2468 | | /* Restore UID and security context */ |
2469 | 0 | SetUserIdAndSecContext(save_userid, save_sec_context); |
2470 | | |
2471 | | /* Save the plan */ |
2472 | 0 | SPI_keepplan(qplan); |
2473 | 0 | ri_HashPreparedPlan(qkey, qplan); |
2474 | |
|
2475 | 0 | return qplan; |
2476 | 0 | } |
2477 | | |
2478 | | /* |
2479 | | * Perform a query to enforce an RI restriction |
2480 | | */ |
2481 | | static bool |
2482 | | ri_PerformCheck(const RI_ConstraintInfo *riinfo, |
2483 | | RI_QueryKey *qkey, SPIPlanPtr qplan, |
2484 | | Relation fk_rel, Relation pk_rel, |
2485 | | TupleTableSlot *oldslot, TupleTableSlot *newslot, |
2486 | | bool is_restrict, |
2487 | | bool detectNewRows, int expect_OK) |
2488 | 0 | { |
2489 | 0 | Relation query_rel, |
2490 | 0 | source_rel; |
2491 | 0 | bool source_is_pk; |
2492 | 0 | Snapshot test_snapshot; |
2493 | 0 | Snapshot crosscheck_snapshot; |
2494 | 0 | int limit; |
2495 | 0 | int spi_result; |
2496 | 0 | Oid save_userid; |
2497 | 0 | int save_sec_context; |
2498 | 0 | Datum vals[RI_MAX_NUMKEYS * 2]; |
2499 | 0 | char nulls[RI_MAX_NUMKEYS * 2]; |
2500 | | |
2501 | | /* |
2502 | | * Use the query type code to determine whether the query is run against |
2503 | | * the PK or FK table; we'll do the check as that table's owner |
2504 | | */ |
2505 | 0 | if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK) |
2506 | 0 | query_rel = pk_rel; |
2507 | 0 | else |
2508 | 0 | query_rel = fk_rel; |
2509 | | |
2510 | | /* |
2511 | | * The values for the query are taken from the table on which the trigger |
2512 | | * is called - it is normally the other one with respect to query_rel. An |
2513 | | * exception is ri_Check_Pk_Match(), which uses the PK table for both (and |
2514 | | * sets queryno to RI_PLAN_CHECK_LOOKUPPK_FROM_PK). We might eventually |
2515 | | * need some less klugy way to determine this. |
2516 | | */ |
2517 | 0 | if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK) |
2518 | 0 | { |
2519 | 0 | source_rel = fk_rel; |
2520 | 0 | source_is_pk = false; |
2521 | 0 | } |
2522 | 0 | else |
2523 | 0 | { |
2524 | 0 | source_rel = pk_rel; |
2525 | 0 | source_is_pk = true; |
2526 | 0 | } |
2527 | | |
2528 | | /* Extract the parameters to be passed into the query */ |
2529 | 0 | if (newslot) |
2530 | 0 | { |
2531 | 0 | ri_ExtractValues(source_rel, newslot, riinfo, source_is_pk, |
2532 | 0 | vals, nulls); |
2533 | 0 | if (oldslot) |
2534 | 0 | ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk, |
2535 | 0 | vals + riinfo->nkeys, nulls + riinfo->nkeys); |
2536 | 0 | } |
2537 | 0 | else |
2538 | 0 | { |
2539 | 0 | ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk, |
2540 | 0 | vals, nulls); |
2541 | 0 | } |
2542 | | |
2543 | | /* |
2544 | | * In READ COMMITTED mode, we just need to use an up-to-date regular |
2545 | | * snapshot, and we will see all rows that could be interesting. But in |
2546 | | * transaction-snapshot mode, we can't change the transaction snapshot. If |
2547 | | * the caller passes detectNewRows == false then it's okay to do the query |
2548 | | * with the transaction snapshot; otherwise we use a current snapshot, and |
2549 | | * tell the executor to error out if it finds any rows under the current |
2550 | | * snapshot that wouldn't be visible per the transaction snapshot. Note |
2551 | | * that SPI_execute_snapshot will register the snapshots, so we don't need |
2552 | | * to bother here. |
2553 | | */ |
2554 | 0 | if (IsolationUsesXactSnapshot() && detectNewRows) |
2555 | 0 | { |
2556 | 0 | CommandCounterIncrement(); /* be sure all my own work is visible */ |
2557 | 0 | test_snapshot = GetLatestSnapshot(); |
2558 | 0 | crosscheck_snapshot = GetTransactionSnapshot(); |
2559 | 0 | } |
2560 | 0 | else |
2561 | 0 | { |
2562 | | /* the default SPI behavior is okay */ |
2563 | 0 | test_snapshot = InvalidSnapshot; |
2564 | 0 | crosscheck_snapshot = InvalidSnapshot; |
2565 | 0 | } |
2566 | | |
2567 | | /* |
2568 | | * If this is a select query (e.g., for a 'no action' or 'restrict' |
2569 | | * trigger), we only need to see if there is a single row in the table, |
2570 | | * matching the key. Otherwise, limit = 0 - because we want the query to |
2571 | | * affect ALL the matching rows. |
2572 | | */ |
2573 | 0 | limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0; |
2574 | | |
2575 | | /* Switch to proper UID to perform check as */ |
2576 | 0 | GetUserIdAndSecContext(&save_userid, &save_sec_context); |
2577 | 0 | SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner, |
2578 | 0 | save_sec_context | SECURITY_LOCAL_USERID_CHANGE | |
2579 | 0 | SECURITY_NOFORCE_RLS); |
2580 | | |
2581 | | /* Finally we can run the query. */ |
2582 | 0 | spi_result = SPI_execute_snapshot(qplan, |
2583 | 0 | vals, nulls, |
2584 | 0 | test_snapshot, crosscheck_snapshot, |
2585 | 0 | false, false, limit); |
2586 | | |
2587 | | /* Restore UID and security context */ |
2588 | 0 | SetUserIdAndSecContext(save_userid, save_sec_context); |
2589 | | |
2590 | | /* Check result */ |
2591 | 0 | if (spi_result < 0) |
2592 | 0 | elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result)); |
2593 | | |
2594 | 0 | if (expect_OK >= 0 && spi_result != expect_OK) |
2595 | 0 | ereport(ERROR, |
2596 | 0 | (errcode(ERRCODE_INTERNAL_ERROR), |
2597 | 0 | errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result", |
2598 | 0 | RelationGetRelationName(pk_rel), |
2599 | 0 | NameStr(riinfo->conname), |
2600 | 0 | RelationGetRelationName(fk_rel)), |
2601 | 0 | errhint("This is most likely due to a rule having rewritten the query."))); |
2602 | | |
2603 | | /* XXX wouldn't it be clearer to do this part at the caller? */ |
2604 | 0 | if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK && |
2605 | 0 | expect_OK == SPI_OK_SELECT && |
2606 | 0 | (SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)) |
2607 | 0 | ri_ReportViolation(riinfo, |
2608 | 0 | pk_rel, fk_rel, |
2609 | 0 | newslot ? newslot : oldslot, |
2610 | 0 | NULL, |
2611 | 0 | qkey->constr_queryno, is_restrict, false); |
2612 | |
|
2613 | 0 | return SPI_processed != 0; |
2614 | 0 | } |
2615 | | |
2616 | | /* |
2617 | | * Extract fields from a tuple into Datum/nulls arrays |
2618 | | */ |
2619 | | static void |
2620 | | ri_ExtractValues(Relation rel, TupleTableSlot *slot, |
2621 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk, |
2622 | | Datum *vals, char *nulls) |
2623 | 0 | { |
2624 | 0 | const int16 *attnums; |
2625 | 0 | bool isnull; |
2626 | |
|
2627 | 0 | if (rel_is_pk) |
2628 | 0 | attnums = riinfo->pk_attnums; |
2629 | 0 | else |
2630 | 0 | attnums = riinfo->fk_attnums; |
2631 | |
|
2632 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
2633 | 0 | { |
2634 | 0 | vals[i] = slot_getattr(slot, attnums[i], &isnull); |
2635 | 0 | nulls[i] = isnull ? 'n' : ' '; |
2636 | 0 | } |
2637 | 0 | } |
2638 | | |
2639 | | /* |
2640 | | * Produce an error report |
2641 | | * |
2642 | | * If the failed constraint was on insert/update to the FK table, |
2643 | | * we want the key names and values extracted from there, and the error |
2644 | | * message to look like 'key blah is not present in PK'. |
2645 | | * Otherwise, the attr names and values come from the PK table and the |
2646 | | * message looks like 'key blah is still referenced from FK'. |
2647 | | */ |
2648 | | static void |
2649 | | ri_ReportViolation(const RI_ConstraintInfo *riinfo, |
2650 | | Relation pk_rel, Relation fk_rel, |
2651 | | TupleTableSlot *violatorslot, TupleDesc tupdesc, |
2652 | | int queryno, bool is_restrict, bool partgone) |
2653 | 0 | { |
2654 | 0 | StringInfoData key_names; |
2655 | 0 | StringInfoData key_values; |
2656 | 0 | bool onfk; |
2657 | 0 | const int16 *attnums; |
2658 | 0 | Oid rel_oid; |
2659 | 0 | AclResult aclresult; |
2660 | 0 | bool has_perm = true; |
2661 | | |
2662 | | /* |
2663 | | * Determine which relation to complain about. If tupdesc wasn't passed |
2664 | | * by caller, assume the violator tuple came from there. |
2665 | | */ |
2666 | 0 | onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK); |
2667 | 0 | if (onfk) |
2668 | 0 | { |
2669 | 0 | attnums = riinfo->fk_attnums; |
2670 | 0 | rel_oid = fk_rel->rd_id; |
2671 | 0 | if (tupdesc == NULL) |
2672 | 0 | tupdesc = fk_rel->rd_att; |
2673 | 0 | } |
2674 | 0 | else |
2675 | 0 | { |
2676 | 0 | attnums = riinfo->pk_attnums; |
2677 | 0 | rel_oid = pk_rel->rd_id; |
2678 | 0 | if (tupdesc == NULL) |
2679 | 0 | tupdesc = pk_rel->rd_att; |
2680 | 0 | } |
2681 | | |
2682 | | /* |
2683 | | * Check permissions- if the user does not have access to view the data in |
2684 | | * any of the key columns then we don't include the errdetail() below. |
2685 | | * |
2686 | | * Check if RLS is enabled on the relation first. If so, we don't return |
2687 | | * any specifics to avoid leaking data. |
2688 | | * |
2689 | | * Check table-level permissions next and, failing that, column-level |
2690 | | * privileges. |
2691 | | * |
2692 | | * When a partition at the referenced side is being detached/dropped, we |
2693 | | * needn't check, since the user must be the table owner anyway. |
2694 | | */ |
2695 | 0 | if (partgone) |
2696 | 0 | has_perm = true; |
2697 | 0 | else if (check_enable_rls(rel_oid, InvalidOid, true) != RLS_ENABLED) |
2698 | 0 | { |
2699 | 0 | aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT); |
2700 | 0 | if (aclresult != ACLCHECK_OK) |
2701 | 0 | { |
2702 | | /* Try for column-level permissions */ |
2703 | 0 | for (int idx = 0; idx < riinfo->nkeys; idx++) |
2704 | 0 | { |
2705 | 0 | aclresult = pg_attribute_aclcheck(rel_oid, attnums[idx], |
2706 | 0 | GetUserId(), |
2707 | 0 | ACL_SELECT); |
2708 | | |
2709 | | /* No access to the key */ |
2710 | 0 | if (aclresult != ACLCHECK_OK) |
2711 | 0 | { |
2712 | 0 | has_perm = false; |
2713 | 0 | break; |
2714 | 0 | } |
2715 | 0 | } |
2716 | 0 | } |
2717 | 0 | } |
2718 | 0 | else |
2719 | 0 | has_perm = false; |
2720 | |
|
2721 | 0 | if (has_perm) |
2722 | 0 | { |
2723 | | /* Get printable versions of the keys involved */ |
2724 | 0 | initStringInfo(&key_names); |
2725 | 0 | initStringInfo(&key_values); |
2726 | 0 | for (int idx = 0; idx < riinfo->nkeys; idx++) |
2727 | 0 | { |
2728 | 0 | int fnum = attnums[idx]; |
2729 | 0 | Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1); |
2730 | 0 | char *name, |
2731 | 0 | *val; |
2732 | 0 | Datum datum; |
2733 | 0 | bool isnull; |
2734 | |
|
2735 | 0 | name = NameStr(att->attname); |
2736 | |
|
2737 | 0 | datum = slot_getattr(violatorslot, fnum, &isnull); |
2738 | 0 | if (!isnull) |
2739 | 0 | { |
2740 | 0 | Oid foutoid; |
2741 | 0 | bool typisvarlena; |
2742 | |
|
2743 | 0 | getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena); |
2744 | 0 | val = OidOutputFunctionCall(foutoid, datum); |
2745 | 0 | } |
2746 | 0 | else |
2747 | 0 | val = "null"; |
2748 | |
|
2749 | 0 | if (idx > 0) |
2750 | 0 | { |
2751 | 0 | appendStringInfoString(&key_names, ", "); |
2752 | 0 | appendStringInfoString(&key_values, ", "); |
2753 | 0 | } |
2754 | 0 | appendStringInfoString(&key_names, name); |
2755 | 0 | appendStringInfoString(&key_values, val); |
2756 | 0 | } |
2757 | 0 | } |
2758 | |
|
2759 | 0 | if (partgone) |
2760 | 0 | ereport(ERROR, |
2761 | 0 | (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), |
2762 | 0 | errmsg("removing partition \"%s\" violates foreign key constraint \"%s\"", |
2763 | 0 | RelationGetRelationName(pk_rel), |
2764 | 0 | NameStr(riinfo->conname)), |
2765 | 0 | errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", |
2766 | 0 | key_names.data, key_values.data, |
2767 | 0 | RelationGetRelationName(fk_rel)), |
2768 | 0 | errtableconstraint(fk_rel, NameStr(riinfo->conname)))); |
2769 | 0 | else if (onfk) |
2770 | 0 | ereport(ERROR, |
2771 | 0 | (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), |
2772 | 0 | errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", |
2773 | 0 | RelationGetRelationName(fk_rel), |
2774 | 0 | NameStr(riinfo->conname)), |
2775 | 0 | has_perm ? |
2776 | 0 | errdetail("Key (%s)=(%s) is not present in table \"%s\".", |
2777 | 0 | key_names.data, key_values.data, |
2778 | 0 | RelationGetRelationName(pk_rel)) : |
2779 | 0 | errdetail("Key is not present in table \"%s\".", |
2780 | 0 | RelationGetRelationName(pk_rel)), |
2781 | 0 | errtableconstraint(fk_rel, NameStr(riinfo->conname)))); |
2782 | 0 | else if (is_restrict) |
2783 | 0 | ereport(ERROR, |
2784 | 0 | (errcode(ERRCODE_RESTRICT_VIOLATION), |
2785 | 0 | errmsg("update or delete on table \"%s\" violates RESTRICT setting of foreign key constraint \"%s\" on table \"%s\"", |
2786 | 0 | RelationGetRelationName(pk_rel), |
2787 | 0 | NameStr(riinfo->conname), |
2788 | 0 | RelationGetRelationName(fk_rel)), |
2789 | 0 | has_perm ? |
2790 | 0 | errdetail("Key (%s)=(%s) is referenced from table \"%s\".", |
2791 | 0 | key_names.data, key_values.data, |
2792 | 0 | RelationGetRelationName(fk_rel)) : |
2793 | 0 | errdetail("Key is referenced from table \"%s\".", |
2794 | 0 | RelationGetRelationName(fk_rel)), |
2795 | 0 | errtableconstraint(fk_rel, NameStr(riinfo->conname)))); |
2796 | 0 | else |
2797 | 0 | ereport(ERROR, |
2798 | 0 | (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), |
2799 | 0 | errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"", |
2800 | 0 | RelationGetRelationName(pk_rel), |
2801 | 0 | NameStr(riinfo->conname), |
2802 | 0 | RelationGetRelationName(fk_rel)), |
2803 | 0 | has_perm ? |
2804 | 0 | errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", |
2805 | 0 | key_names.data, key_values.data, |
2806 | 0 | RelationGetRelationName(fk_rel)) : |
2807 | 0 | errdetail("Key is still referenced from table \"%s\".", |
2808 | 0 | RelationGetRelationName(fk_rel)), |
2809 | 0 | errtableconstraint(fk_rel, NameStr(riinfo->conname)))); |
2810 | 0 | } |
2811 | | |
2812 | | |
2813 | | /* |
2814 | | * ri_NullCheck - |
2815 | | * |
2816 | | * Determine the NULL state of all key values in a tuple |
2817 | | * |
2818 | | * Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL. |
2819 | | */ |
2820 | | static int |
2821 | | ri_NullCheck(TupleDesc tupDesc, |
2822 | | TupleTableSlot *slot, |
2823 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk) |
2824 | 0 | { |
2825 | 0 | const int16 *attnums; |
2826 | 0 | bool allnull = true; |
2827 | 0 | bool nonenull = true; |
2828 | |
|
2829 | 0 | if (rel_is_pk) |
2830 | 0 | attnums = riinfo->pk_attnums; |
2831 | 0 | else |
2832 | 0 | attnums = riinfo->fk_attnums; |
2833 | |
|
2834 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
2835 | 0 | { |
2836 | 0 | if (slot_attisnull(slot, attnums[i])) |
2837 | 0 | nonenull = false; |
2838 | 0 | else |
2839 | 0 | allnull = false; |
2840 | 0 | } |
2841 | |
|
2842 | 0 | if (allnull) |
2843 | 0 | return RI_KEYS_ALL_NULL; |
2844 | | |
2845 | 0 | if (nonenull) |
2846 | 0 | return RI_KEYS_NONE_NULL; |
2847 | | |
2848 | 0 | return RI_KEYS_SOME_NULL; |
2849 | 0 | } |
2850 | | |
2851 | | |
2852 | | /* |
2853 | | * ri_InitHashTables - |
2854 | | * |
2855 | | * Initialize our internal hash tables. |
2856 | | */ |
2857 | | static void |
2858 | | ri_InitHashTables(void) |
2859 | 0 | { |
2860 | 0 | HASHCTL ctl; |
2861 | |
|
2862 | 0 | ctl.keysize = sizeof(Oid); |
2863 | 0 | ctl.entrysize = sizeof(RI_ConstraintInfo); |
2864 | 0 | ri_constraint_cache = hash_create("RI constraint cache", |
2865 | 0 | RI_INIT_CONSTRAINTHASHSIZE, |
2866 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
2867 | | |
2868 | | /* Arrange to flush cache on pg_constraint changes */ |
2869 | 0 | CacheRegisterSyscacheCallback(CONSTROID, |
2870 | 0 | InvalidateConstraintCacheCallBack, |
2871 | 0 | (Datum) 0); |
2872 | |
|
2873 | 0 | ctl.keysize = sizeof(RI_QueryKey); |
2874 | 0 | ctl.entrysize = sizeof(RI_QueryHashEntry); |
2875 | 0 | ri_query_cache = hash_create("RI query cache", |
2876 | 0 | RI_INIT_QUERYHASHSIZE, |
2877 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
2878 | |
|
2879 | 0 | ctl.keysize = sizeof(RI_CompareKey); |
2880 | 0 | ctl.entrysize = sizeof(RI_CompareHashEntry); |
2881 | 0 | ri_compare_cache = hash_create("RI compare cache", |
2882 | 0 | RI_INIT_QUERYHASHSIZE, |
2883 | 0 | &ctl, HASH_ELEM | HASH_BLOBS); |
2884 | 0 | } |
2885 | | |
2886 | | |
2887 | | /* |
2888 | | * ri_FetchPreparedPlan - |
2889 | | * |
2890 | | * Lookup for a query key in our private hash table of prepared |
2891 | | * and saved SPI execution plans. Return the plan if found or NULL. |
2892 | | */ |
2893 | | static SPIPlanPtr |
2894 | | ri_FetchPreparedPlan(RI_QueryKey *key) |
2895 | 0 | { |
2896 | 0 | RI_QueryHashEntry *entry; |
2897 | 0 | SPIPlanPtr plan; |
2898 | | |
2899 | | /* |
2900 | | * On the first call initialize the hashtable |
2901 | | */ |
2902 | 0 | if (!ri_query_cache) |
2903 | 0 | ri_InitHashTables(); |
2904 | | |
2905 | | /* |
2906 | | * Lookup for the key |
2907 | | */ |
2908 | 0 | entry = (RI_QueryHashEntry *) hash_search(ri_query_cache, |
2909 | 0 | key, |
2910 | 0 | HASH_FIND, NULL); |
2911 | 0 | if (entry == NULL) |
2912 | 0 | return NULL; |
2913 | | |
2914 | | /* |
2915 | | * Check whether the plan is still valid. If it isn't, we don't want to |
2916 | | * simply rely on plancache.c to regenerate it; rather we should start |
2917 | | * from scratch and rebuild the query text too. This is to cover cases |
2918 | | * such as table/column renames. We depend on the plancache machinery to |
2919 | | * detect possible invalidations, though. |
2920 | | * |
2921 | | * CAUTION: this check is only trustworthy if the caller has already |
2922 | | * locked both FK and PK rels. |
2923 | | */ |
2924 | 0 | plan = entry->plan; |
2925 | 0 | if (plan && SPI_plan_is_valid(plan)) |
2926 | 0 | return plan; |
2927 | | |
2928 | | /* |
2929 | | * Otherwise we might as well flush the cached plan now, to free a little |
2930 | | * memory space before we make a new one. |
2931 | | */ |
2932 | 0 | entry->plan = NULL; |
2933 | 0 | if (plan) |
2934 | 0 | SPI_freeplan(plan); |
2935 | |
|
2936 | 0 | return NULL; |
2937 | 0 | } |
2938 | | |
2939 | | |
2940 | | /* |
2941 | | * ri_HashPreparedPlan - |
2942 | | * |
2943 | | * Add another plan to our private SPI query plan hashtable. |
2944 | | */ |
2945 | | static void |
2946 | | ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan) |
2947 | 0 | { |
2948 | 0 | RI_QueryHashEntry *entry; |
2949 | 0 | bool found; |
2950 | | |
2951 | | /* |
2952 | | * On the first call initialize the hashtable |
2953 | | */ |
2954 | 0 | if (!ri_query_cache) |
2955 | 0 | ri_InitHashTables(); |
2956 | | |
2957 | | /* |
2958 | | * Add the new plan. We might be overwriting an entry previously found |
2959 | | * invalid by ri_FetchPreparedPlan. |
2960 | | */ |
2961 | 0 | entry = (RI_QueryHashEntry *) hash_search(ri_query_cache, |
2962 | 0 | key, |
2963 | 0 | HASH_ENTER, &found); |
2964 | 0 | Assert(!found || entry->plan == NULL); |
2965 | 0 | entry->plan = plan; |
2966 | 0 | } |
2967 | | |
2968 | | |
2969 | | /* |
2970 | | * ri_KeysEqual - |
2971 | | * |
2972 | | * Check if all key values in OLD and NEW are "equivalent": |
2973 | | * For normal FKs we check for equality. |
2974 | | * For temporal FKs we check that the PK side is a superset of its old value, |
2975 | | * or the FK side is a subset of its old value. |
2976 | | * |
2977 | | * Note: at some point we might wish to redefine this as checking for |
2978 | | * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be |
2979 | | * considered equal. Currently there is no need since all callers have |
2980 | | * previously found at least one of the rows to contain no nulls. |
2981 | | */ |
2982 | | static bool |
2983 | | ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, |
2984 | | const RI_ConstraintInfo *riinfo, bool rel_is_pk) |
2985 | 0 | { |
2986 | 0 | const int16 *attnums; |
2987 | |
|
2988 | 0 | if (rel_is_pk) |
2989 | 0 | attnums = riinfo->pk_attnums; |
2990 | 0 | else |
2991 | 0 | attnums = riinfo->fk_attnums; |
2992 | | |
2993 | | /* XXX: could be worthwhile to fetch all necessary attrs at once */ |
2994 | 0 | for (int i = 0; i < riinfo->nkeys; i++) |
2995 | 0 | { |
2996 | 0 | Datum oldvalue; |
2997 | 0 | Datum newvalue; |
2998 | 0 | bool isnull; |
2999 | | |
3000 | | /* |
3001 | | * Get one attribute's oldvalue. If it is NULL - they're not equal. |
3002 | | */ |
3003 | 0 | oldvalue = slot_getattr(oldslot, attnums[i], &isnull); |
3004 | 0 | if (isnull) |
3005 | 0 | return false; |
3006 | | |
3007 | | /* |
3008 | | * Get one attribute's newvalue. If it is NULL - they're not equal. |
3009 | | */ |
3010 | 0 | newvalue = slot_getattr(newslot, attnums[i], &isnull); |
3011 | 0 | if (isnull) |
3012 | 0 | return false; |
3013 | | |
3014 | 0 | if (rel_is_pk) |
3015 | 0 | { |
3016 | | /* |
3017 | | * If we are looking at the PK table, then do a bytewise |
3018 | | * comparison. We must propagate PK changes if the value is |
3019 | | * changed to one that "looks" different but would compare as |
3020 | | * equal using the equality operator. This only makes a |
3021 | | * difference for ON UPDATE CASCADE, but for consistency we treat |
3022 | | * all changes to the PK the same. |
3023 | | */ |
3024 | 0 | CompactAttribute *att = TupleDescCompactAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1); |
3025 | |
|
3026 | 0 | if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen)) |
3027 | 0 | return false; |
3028 | 0 | } |
3029 | 0 | else |
3030 | 0 | { |
3031 | 0 | Oid eq_opr; |
3032 | | |
3033 | | /* |
3034 | | * When comparing the PERIOD columns we can skip the check |
3035 | | * whenever the referencing column stayed equal or shrank, so test |
3036 | | * with the contained-by operator instead. |
3037 | | */ |
3038 | 0 | if (riinfo->hasperiod && i == riinfo->nkeys - 1) |
3039 | 0 | eq_opr = riinfo->period_contained_by_oper; |
3040 | 0 | else |
3041 | 0 | eq_opr = riinfo->ff_eq_oprs[i]; |
3042 | | |
3043 | | /* |
3044 | | * For the FK table, compare with the appropriate equality |
3045 | | * operator. Changes that compare equal will still satisfy the |
3046 | | * constraint after the update. |
3047 | | */ |
3048 | 0 | if (!ri_CompareWithCast(eq_opr, RIAttType(rel, attnums[i]), RIAttCollation(rel, attnums[i]), |
3049 | 0 | newvalue, oldvalue)) |
3050 | 0 | return false; |
3051 | 0 | } |
3052 | 0 | } |
3053 | | |
3054 | 0 | return true; |
3055 | 0 | } |
3056 | | |
3057 | | |
3058 | | /* |
3059 | | * ri_CompareWithCast - |
3060 | | * |
3061 | | * Call the appropriate comparison operator for two values. |
3062 | | * Normally this is equality, but for the PERIOD part of foreign keys |
3063 | | * it is ContainedBy, so the order of lhs vs rhs is significant. |
3064 | | * See below for how the collation is applied. |
3065 | | * |
3066 | | * NB: we have already checked that neither value is null. |
3067 | | */ |
3068 | | static bool |
3069 | | ri_CompareWithCast(Oid eq_opr, Oid typeid, Oid collid, |
3070 | | Datum lhs, Datum rhs) |
3071 | 0 | { |
3072 | 0 | RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid); |
3073 | | |
3074 | | /* Do we need to cast the values? */ |
3075 | 0 | if (OidIsValid(entry->cast_func_finfo.fn_oid)) |
3076 | 0 | { |
3077 | 0 | lhs = FunctionCall3(&entry->cast_func_finfo, |
3078 | 0 | lhs, |
3079 | 0 | Int32GetDatum(-1), /* typmod */ |
3080 | 0 | BoolGetDatum(false)); /* implicit coercion */ |
3081 | 0 | rhs = FunctionCall3(&entry->cast_func_finfo, |
3082 | 0 | rhs, |
3083 | 0 | Int32GetDatum(-1), /* typmod */ |
3084 | 0 | BoolGetDatum(false)); /* implicit coercion */ |
3085 | 0 | } |
3086 | | |
3087 | | /* |
3088 | | * Apply the comparison operator. |
3089 | | * |
3090 | | * Note: This function is part of a call stack that determines whether an |
3091 | | * update to a row is significant enough that it needs checking or action |
3092 | | * on the other side of a foreign-key constraint. Therefore, the |
3093 | | * comparison here would need to be done with the collation of the *other* |
3094 | | * table. For simplicity (e.g., we might not even have the other table |
3095 | | * open), we'll use our own collation. This is fine because we require |
3096 | | * that both collations have the same notion of equality (either they are |
3097 | | * both deterministic or else they are both the same). |
3098 | | * |
3099 | | * With range/multirangetypes, the collation of the base type is stored as |
3100 | | * part of the rangetype (pg_range.rngcollation), and always used, so |
3101 | | * there is no danger of inconsistency even using a non-equals operator. |
3102 | | * But if we support arbitrary types with PERIOD, we should perhaps just |
3103 | | * always force a re-check. |
3104 | | */ |
3105 | 0 | return DatumGetBool(FunctionCall2Coll(&entry->eq_opr_finfo, collid, lhs, rhs)); |
3106 | 0 | } |
3107 | | |
3108 | | /* |
3109 | | * ri_HashCompareOp - |
3110 | | * |
3111 | | * See if we know how to compare two values, and create a new hash entry |
3112 | | * if not. |
3113 | | */ |
3114 | | static RI_CompareHashEntry * |
3115 | | ri_HashCompareOp(Oid eq_opr, Oid typeid) |
3116 | 0 | { |
3117 | 0 | RI_CompareKey key; |
3118 | 0 | RI_CompareHashEntry *entry; |
3119 | 0 | bool found; |
3120 | | |
3121 | | /* |
3122 | | * On the first call initialize the hashtable |
3123 | | */ |
3124 | 0 | if (!ri_compare_cache) |
3125 | 0 | ri_InitHashTables(); |
3126 | | |
3127 | | /* |
3128 | | * Find or create a hash entry. Note we're assuming RI_CompareKey |
3129 | | * contains no struct padding. |
3130 | | */ |
3131 | 0 | key.eq_opr = eq_opr; |
3132 | 0 | key.typeid = typeid; |
3133 | 0 | entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache, |
3134 | 0 | &key, |
3135 | 0 | HASH_ENTER, &found); |
3136 | 0 | if (!found) |
3137 | 0 | entry->valid = false; |
3138 | | |
3139 | | /* |
3140 | | * If not already initialized, do so. Since we'll keep this hash entry |
3141 | | * for the life of the backend, put any subsidiary info for the function |
3142 | | * cache structs into TopMemoryContext. |
3143 | | */ |
3144 | 0 | if (!entry->valid) |
3145 | 0 | { |
3146 | 0 | Oid lefttype, |
3147 | 0 | righttype, |
3148 | 0 | castfunc; |
3149 | 0 | CoercionPathType pathtype; |
3150 | | |
3151 | | /* We always need to know how to call the equality operator */ |
3152 | 0 | fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo, |
3153 | 0 | TopMemoryContext); |
3154 | | |
3155 | | /* |
3156 | | * If we chose to use a cast from FK to PK type, we may have to apply |
3157 | | * the cast function to get to the operator's input type. |
3158 | | * |
3159 | | * XXX eventually it would be good to support array-coercion cases |
3160 | | * here and in ri_CompareWithCast(). At the moment there is no point |
3161 | | * because cases involving nonidentical array types will be rejected |
3162 | | * at constraint creation time. |
3163 | | * |
3164 | | * XXX perhaps also consider supporting CoerceViaIO? No need at the |
3165 | | * moment since that will never be generated for implicit coercions. |
3166 | | */ |
3167 | 0 | op_input_types(eq_opr, &lefttype, &righttype); |
3168 | 0 | Assert(lefttype == righttype); |
3169 | 0 | if (typeid == lefttype) |
3170 | 0 | castfunc = InvalidOid; /* simplest case */ |
3171 | 0 | else |
3172 | 0 | { |
3173 | 0 | pathtype = find_coercion_pathway(lefttype, typeid, |
3174 | 0 | COERCION_IMPLICIT, |
3175 | 0 | &castfunc); |
3176 | 0 | if (pathtype != COERCION_PATH_FUNC && |
3177 | 0 | pathtype != COERCION_PATH_RELABELTYPE) |
3178 | 0 | { |
3179 | | /* |
3180 | | * The declared input type of the eq_opr might be a |
3181 | | * polymorphic type such as ANYARRAY or ANYENUM, or other |
3182 | | * special cases such as RECORD; find_coercion_pathway |
3183 | | * currently doesn't subsume these special cases. |
3184 | | */ |
3185 | 0 | if (!IsBinaryCoercible(typeid, lefttype)) |
3186 | 0 | elog(ERROR, "no conversion function from %s to %s", |
3187 | 0 | format_type_be(typeid), |
3188 | 0 | format_type_be(lefttype)); |
3189 | 0 | } |
3190 | 0 | } |
3191 | 0 | if (OidIsValid(castfunc)) |
3192 | 0 | fmgr_info_cxt(castfunc, &entry->cast_func_finfo, |
3193 | 0 | TopMemoryContext); |
3194 | 0 | else |
3195 | 0 | entry->cast_func_finfo.fn_oid = InvalidOid; |
3196 | 0 | entry->valid = true; |
3197 | 0 | } |
3198 | | |
3199 | 0 | return entry; |
3200 | 0 | } |
3201 | | |
3202 | | |
3203 | | /* |
3204 | | * Given a trigger function OID, determine whether it is an RI trigger, |
3205 | | * and if so whether it is attached to PK or FK relation. |
3206 | | */ |
3207 | | int |
3208 | | RI_FKey_trigger_type(Oid tgfoid) |
3209 | 0 | { |
3210 | 0 | switch (tgfoid) |
3211 | 0 | { |
3212 | 0 | case F_RI_FKEY_CASCADE_DEL: |
3213 | 0 | case F_RI_FKEY_CASCADE_UPD: |
3214 | 0 | case F_RI_FKEY_RESTRICT_DEL: |
3215 | 0 | case F_RI_FKEY_RESTRICT_UPD: |
3216 | 0 | case F_RI_FKEY_SETNULL_DEL: |
3217 | 0 | case F_RI_FKEY_SETNULL_UPD: |
3218 | 0 | case F_RI_FKEY_SETDEFAULT_DEL: |
3219 | 0 | case F_RI_FKEY_SETDEFAULT_UPD: |
3220 | 0 | case F_RI_FKEY_NOACTION_DEL: |
3221 | 0 | case F_RI_FKEY_NOACTION_UPD: |
3222 | 0 | return RI_TRIGGER_PK; |
3223 | | |
3224 | 0 | case F_RI_FKEY_CHECK_INS: |
3225 | 0 | case F_RI_FKEY_CHECK_UPD: |
3226 | 0 | return RI_TRIGGER_FK; |
3227 | 0 | } |
3228 | | |
3229 | 0 | return RI_TRIGGER_NONE; |
3230 | 0 | } |