Age Owner Branch data TLA Line data Source code
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-2026, 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/genam.h"
27 : : #include "access/htup_details.h"
28 : : #include "access/skey.h"
29 : : #include "access/sysattr.h"
30 : : #include "access/table.h"
31 : : #include "access/tableam.h"
32 : : #include "access/xact.h"
33 : : #include "catalog/index.h"
34 : : #include "catalog/namespace.h"
35 : : #include "catalog/objectaccess.h"
36 : : #include "catalog/pg_am_d.h"
37 : : #include "catalog/pg_collation.h"
38 : : #include "catalog/pg_constraint.h"
39 : : #include "catalog/pg_index.h"
40 : : #include "catalog/pg_namespace.h"
41 : : #include "catalog/pg_proc.h"
42 : : #include "commands/trigger.h"
43 : : #include "executor/executor.h"
44 : : #include "executor/spi.h"
45 : : #include "lib/ilist.h"
46 : : #include "miscadmin.h"
47 : : #include "parser/parse_coerce.h"
48 : : #include "parser/parse_relation.h"
49 : : #include "tcop/utility.h"
50 : : #include "utils/acl.h"
51 : : #include "utils/builtins.h"
52 : : #include "utils/datum.h"
53 : : #include "utils/fmgroids.h"
54 : : #include "utils/guc.h"
55 : : #include "utils/hsearch.h"
56 : : #include "utils/injection_point.h"
57 : : #include "utils/inval.h"
58 : : #include "utils/lsyscache.h"
59 : : #include "utils/memutils.h"
60 : : #include "utils/rel.h"
61 : : #include "utils/rls.h"
62 : : #include "utils/ruleutils.h"
63 : : #include "utils/snapmgr.h"
64 : : #include "utils/syscache.h"
65 : :
66 : : /*
67 : : * Local definitions
68 : : */
69 : :
70 : : #define RI_MAX_NUMKEYS INDEX_MAX_KEYS
71 : :
72 : : #define RI_INIT_CONSTRAINTHASHSIZE 64
73 : : #define RI_INIT_QUERYHASHSIZE (RI_INIT_CONSTRAINTHASHSIZE * 4)
74 : :
75 : : #define RI_KEYS_ALL_NULL 0
76 : : #define RI_KEYS_SOME_NULL 1
77 : : #define RI_KEYS_NONE_NULL 2
78 : :
79 : : /* RI query type codes */
80 : : /* these queries are executed against the PK (referenced) table: */
81 : : #define RI_PLAN_CHECK_LOOKUPPK 1
82 : : #define RI_PLAN_CHECK_LOOKUPPK_FROM_PK 2
83 : : #define RI_PLAN_LAST_ON_PK RI_PLAN_CHECK_LOOKUPPK_FROM_PK
84 : : /* these queries are executed against the FK (referencing) table: */
85 : : #define RI_PLAN_CASCADE_ONDELETE 3
86 : : #define RI_PLAN_CASCADE_ONUPDATE 4
87 : : #define RI_PLAN_NO_ACTION 5
88 : : /* For RESTRICT, the same plan can be used for both ON DELETE and ON UPDATE triggers. */
89 : : #define RI_PLAN_RESTRICT 6
90 : : #define RI_PLAN_SETNULL_ONDELETE 7
91 : : #define RI_PLAN_SETNULL_ONUPDATE 8
92 : : #define RI_PLAN_SETDEFAULT_ONDELETE 9
93 : : #define RI_PLAN_SETDEFAULT_ONUPDATE 10
94 : :
95 : : #define MAX_QUOTED_NAME_LEN (NAMEDATALEN*2+3)
96 : : #define MAX_QUOTED_REL_NAME_LEN (MAX_QUOTED_NAME_LEN*2)
97 : :
98 : : #define RIAttName(rel, attnum) NameStr(*attnumAttName(rel, attnum))
99 : : #define RIAttType(rel, attnum) attnumTypeId(rel, attnum)
100 : : #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
101 : :
102 : : #define RI_TRIGTYPE_INSERT 1
103 : : #define RI_TRIGTYPE_UPDATE 2
104 : : #define RI_TRIGTYPE_DELETE 3
105 : :
106 : : typedef struct FastPathMeta FastPathMeta;
107 : :
108 : : /* Eligibility checks that require the referenced table and index to be open. */
109 : : typedef enum RI_FastPathState
110 : : {
111 : : RI_FASTPATH_UNKNOWN,
112 : : RI_FASTPATH_USABLE,
113 : : RI_FASTPATH_UNUSABLE
114 : : } RI_FastPathState;
115 : :
116 : : /*
117 : : * RI_ConstraintInfo
118 : : *
119 : : * Information extracted from an FK pg_constraint entry. This is cached in
120 : : * ri_constraint_cache.
121 : : *
122 : : * Note that pf/pp/ff_eq_oprs may hold the overlaps operator instead of equals
123 : : * for the PERIOD part of a temporal foreign key.
124 : : */
125 : : typedef struct RI_ConstraintInfo
126 : : {
127 : : Oid constraint_id; /* OID of pg_constraint entry (hash key) */
128 : : bool valid; /* successfully initialized? */
129 : : Oid constraint_root_id; /* OID of topmost ancestor constraint;
130 : : * same as constraint_id if not inherited */
131 : : uint32 oidHashValue; /* hash value of constraint_id */
132 : : uint32 rootHashValue; /* hash value of constraint_root_id */
133 : : NameData conname; /* name of the FK constraint */
134 : : Oid pk_relid; /* referenced relation */
135 : : Oid fk_relid; /* referencing relation */
136 : : char confupdtype; /* foreign key's ON UPDATE action */
137 : : char confdeltype; /* foreign key's ON DELETE action */
138 : : int ndelsetcols; /* number of columns referenced in ON DELETE
139 : : * SET clause */
140 : : int16 confdelsetcols[RI_MAX_NUMKEYS]; /* attnums of cols to set on
141 : : * delete */
142 : : char confmatchtype; /* foreign key's match type */
143 : : bool hasperiod; /* if the foreign key uses PERIOD */
144 : : int nkeys; /* number of key columns */
145 : : int16 pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
146 : : int16 fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
147 : : Oid pf_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = FK) */
148 : : Oid pp_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (PK = PK) */
149 : : Oid ff_eq_oprs[RI_MAX_NUMKEYS]; /* equality operators (FK = FK) */
150 : : Oid period_contained_by_oper; /* anyrange <@ anyrange (or
151 : : * multiranges) */
152 : : Oid agged_period_contained_by_oper; /* fkattr <@ range_agg(pkattr) */
153 : : Oid period_intersect_oper; /* anyrange * anyrange (or
154 : : * multiranges) */
155 : : dlist_node valid_link; /* Link in list of valid entries */
156 : :
157 : : Oid conindid;
158 : : bool pk_is_partitioned;
159 : : RI_FastPathState fastpath_state; /* populated lazily under lock */
160 : :
161 : : FastPathMeta *fpmeta;
162 : : } RI_ConstraintInfo;
163 : :
164 : : /* Fast-path metadata for RI checks on foreign key referencing tables */
165 : : typedef struct FastPathMeta
166 : : {
167 : : RegProcedure regops[RI_MAX_NUMKEYS];
168 : : Oid subtypes[RI_MAX_NUMKEYS];
169 : : int strats[RI_MAX_NUMKEYS];
170 : : AttrNumber index_attnos[RI_MAX_NUMKEYS]; /* index column positions */
171 : :
172 : : /* Link in ri_fpmeta_dead_list while awaiting deferred release */
173 : : struct FastPathMeta *next_dead;
174 : : } FastPathMeta;
175 : :
176 : : /*
177 : : * RI_QueryKey
178 : : *
179 : : * The key identifying a prepared SPI plan in our query hashtable
180 : : */
181 : : typedef struct RI_QueryKey
182 : : {
183 : : Oid constr_id; /* OID of pg_constraint entry */
184 : : int32 constr_queryno; /* query type ID, see RI_PLAN_XXX above */
185 : : } RI_QueryKey;
186 : :
187 : : /*
188 : : * RI_QueryHashEntry
189 : : */
190 : : typedef struct RI_QueryHashEntry
191 : : {
192 : : RI_QueryKey key;
193 : : SPIPlanPtr plan;
194 : : } RI_QueryHashEntry;
195 : :
196 : : /*
197 : : * RI_CompareKey
198 : : *
199 : : * The key identifying an entry showing how to compare two values
200 : : */
201 : : typedef struct RI_CompareKey
202 : : {
203 : : Oid eq_opr; /* the equality operator to apply */
204 : : Oid typeid; /* the data type to apply it to */
205 : : } RI_CompareKey;
206 : :
207 : : /*
208 : : * RI_CompareHashEntry
209 : : */
210 : : typedef struct RI_CompareHashEntry
211 : : {
212 : : RI_CompareKey key;
213 : : bool valid; /* successfully initialized? */
214 : : FmgrInfo eq_opr_finfo; /* call info for equality fn */
215 : : FmgrInfo cast_func_finfo; /* in case we must coerce input */
216 : : } RI_CompareHashEntry;
217 : :
218 : : /*
219 : : * Local data
220 : : */
221 : : static HTAB *ri_constraint_cache = NULL;
222 : : static HTAB *ri_query_cache = NULL;
223 : : static HTAB *ri_compare_cache = NULL;
224 : : static dclist_head ri_constraint_cache_valid_list;
225 : :
226 : : /*
227 : : * FastPathMeta objects detached from their cache entry by invalidation, but
228 : : * possibly still referenced by an RI check further up the stack. Released
229 : : * by AtEOXact_RI(), where no such reference can exist. See
230 : : * InvalidateConstraintCacheCallBack().
231 : : */
232 : : static FastPathMeta *ri_fpmeta_dead_list = NULL;
233 : :
234 : : /*
235 : : * Stack of the plans being executed by ri_PerformCheck() calls currently
236 : : * running, i.e. RI queries whose SPI_execute_snapshot() has not returned.
237 : : * Innermost of those appears last in the array. A plan that
238 : : * ri_FetchPreparedPlan() finds invalid cannot be freed while it is on this
239 : : * stack. It instead goes on ri_plan_dead_list and is freed by
240 : : * ri_FreeDeadPlans() once it is no longer on the stack, which is checked
241 : : * whenever a check finishes normally, or by AtEOXact_RI() at the latest.
242 : : * Both live in TopMemoryContext.
243 : : *
244 : : * ri_PerformCheck() pops its plan whether the query returns or an error
245 : : * is thrown, by restoring the depth it saw at push time, so the stack is
246 : : * exact at all times. A plan on the stack is never freed until the stack
247 : : * is cut back past it, so no entry can dangle.
248 : : */
249 : : static SPIPlanPtr *ri_executing_plans = NULL;
250 : : static int ri_executing_depth = 0;
251 : : static int ri_executing_maxdepth = 0;
252 : : static List *ri_plan_dead_list = NIL;
253 : :
254 : : /*
255 : : * Local function prototypes
256 : : */
257 : : static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
258 : : TupleTableSlot *oldslot,
259 : : const RI_ConstraintInfo *riinfo);
260 : : static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
261 : : static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
262 : : static void quoteOneName(char *buffer, const char *name);
263 : : static void quoteRelationName(char *buffer, Relation rel);
264 : : static void ri_GenerateQual(StringInfo buf,
265 : : const char *sep,
266 : : const char *leftop, Oid leftoptype,
267 : : Oid opoid,
268 : : const char *rightop, Oid rightoptype);
269 : : static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
270 : : static int ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
271 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk);
272 : : static void ri_BuildQueryKey(RI_QueryKey *key,
273 : : const RI_ConstraintInfo *riinfo,
274 : : int32 constr_queryno);
275 : : static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
276 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk);
277 : : static bool ri_CompareWithCast(Oid eq_opr, Oid typeid, Oid collid,
278 : : Datum lhs, Datum rhs);
279 : :
280 : : static void ri_InitHashTables(void);
281 : : static void InvalidateConstraintCacheCallBack(Datum arg, SysCacheIdentifier cacheid,
282 : : uint32 hashvalue);
283 : : static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
284 : : static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
285 : : static int ri_PushExecutingPlan(SPIPlanPtr plan);
286 : : static bool ri_PlanIsExecuting(SPIPlanPtr plan);
287 : : static void ri_FreeDeadPlans(void);
288 : : static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
289 : :
290 : : static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
291 : : int tgkind);
292 : : static RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
293 : : Relation trig_rel, bool rel_is_pk);
294 : : static RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid);
295 : : static Oid get_ri_constraint_root(Oid constrOid);
296 : : static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, const Oid *argtypes,
297 : : RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel);
298 : : static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
299 : : RI_QueryKey *qkey, SPIPlanPtr qplan,
300 : : Relation fk_rel, Relation pk_rel,
301 : : TupleTableSlot *oldslot, TupleTableSlot *newslot,
302 : : bool is_restrict,
303 : : bool detectNewRows, int expect_OK);
304 : : static bool ri_FastPathCheck(RI_ConstraintInfo *riinfo,
305 : : Relation fk_rel, TupleTableSlot *newslot);
306 : : static bool ri_FastPathProbeOne(Relation pk_rel, Relation idx_rel,
307 : : IndexScanDesc scandesc, TupleTableSlot *slot,
308 : : Snapshot snapshot, const RI_ConstraintInfo *riinfo,
309 : : ScanKeyData *skey, int nkeys);
310 : : static bool ri_LockPKTuple(Relation pk_rel, TupleTableSlot *slot, Snapshot snap,
311 : : bool *concurrently_updated);
312 : : static bool ri_fastpath_is_applicable(const RI_ConstraintInfo *riinfo);
313 : : static bool ri_check_fastpath_index(RI_ConstraintInfo *riinfo,
314 : : Relation fk_rel, Relation pk_rel,
315 : : Relation idx_rel);
316 : : static void ri_CheckPermissions(const RI_ConstraintInfo *riinfo,
317 : : Relation query_rel);
318 : : static bool recheck_matched_pk_tuple(Relation idxrel, ScanKeyData *skeys,
319 : : int nkeys, TupleTableSlot *new_slot);
320 : : static void ri_CheckFunctionPermissions(const RI_ConstraintInfo *riinfo,
321 : : const FastPathMeta *fpmeta);
322 : : static void build_index_scankeys(const RI_ConstraintInfo *riinfo,
323 : : const FastPathMeta *fpmeta,
324 : : Relation idx_rel, const Datum *pk_vals,
325 : : ScanKey skeys);
326 : : static void ri_populate_fastpath_metadata(RI_ConstraintInfo *riinfo,
327 : : Relation idx_rel);
328 : : static void ri_ExtractValues(Relation rel, TupleTableSlot *slot,
329 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk,
330 : : Datum *vals, char *nulls);
331 : : pg_noreturn static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
332 : : Relation pk_rel, Relation fk_rel,
333 : : TupleTableSlot *violatorslot, TupleDesc tupdesc,
334 : : int queryno, bool is_restrict, bool partgone);
335 : :
336 : :
337 : : /*
338 : : * RI_FKey_check -
339 : : *
340 : : * Check foreign key existence (combined for INSERT and UPDATE).
341 : : */
342 : : static Datum
5252 tgl@sss.pgh.pa.us 343 :CBC 605914 : RI_FKey_check(TriggerData *trigdata)
344 : : {
345 : : RI_ConstraintInfo *riinfo;
346 : : Relation fk_rel;
347 : : Relation pk_rel;
348 : : TupleTableSlot *newslot;
349 : : RI_QueryKey qkey;
350 : : SPIPlanPtr qplan;
351 : :
5251 352 : 605914 : riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
353 : : trigdata->tg_relation, false);
354 : :
9890 JanWieck@Yahoo.com 355 [ + + ]: 605914 : if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
2809 andres@anarazel.de 356 : 296 : newslot = trigdata->tg_newslot;
357 : : else
358 : 605618 : newslot = trigdata->tg_trigslot;
359 : :
360 : : /*
361 : : * We should not even consider checking the row if it is no longer valid,
362 : : * since it was either deleted (so the deferred check should be skipped)
363 : : * or updated (in which case only the latest version of the row should be
364 : : * checked). Test its liveness according to SnapshotSelf. We need pin
365 : : * and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller
366 : : * should be holding pin, but not lock.
367 : : */
2796 368 [ + + ]: 605914 : if (!table_tuple_satisfies_snapshot(trigdata->tg_relation, newslot, SnapshotSelf))
369 : 40 : return PointerGetDatum(NULL);
370 : :
7381 tgl@sss.pgh.pa.us 371 : 605874 : fk_rel = trigdata->tg_relation;
372 : :
2809 andres@anarazel.de 373 [ + + + - ]: 605874 : switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false))
374 : : {
9768 JanWieck@Yahoo.com 375 : 114 : case RI_KEYS_ALL_NULL:
376 : :
377 : : /*
378 : : * No further check needed - an all-NULL key passes every type of
379 : : * foreign key constraint.
380 : : */
9656 tgl@sss.pgh.pa.us 381 : 114 : return PointerGetDatum(NULL);
382 : :
9768 JanWieck@Yahoo.com 383 : 104 : case RI_KEYS_SOME_NULL:
384 : :
385 : : /*
386 : : * This is the only case that differs between the three kinds of
387 : : * MATCH.
388 : : */
5251 tgl@sss.pgh.pa.us 389 [ + + - ]: 104 : switch (riinfo->confmatchtype)
390 : : {
7204 391 : 24 : case FKCONSTR_MATCH_FULL:
392 : :
393 : : /*
394 : : * Not allowed - MATCH FULL says either all or none of the
395 : : * attributes can be NULLs
396 : : */
8507 397 [ + - ]: 24 : ereport(ERROR,
398 : : (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
399 : : errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
400 : : RelationGetRelationName(fk_rel),
401 : : NameStr(riinfo->conname)),
402 : : errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
403 : : errtableconstraint(fk_rel,
404 : : NameStr(riinfo->conname))));
405 : : return PointerGetDatum(NULL);
406 : :
5254 407 : 80 : case FKCONSTR_MATCH_SIMPLE:
408 : :
409 : : /*
410 : : * MATCH SIMPLE - if ANY column is null, the key passes
411 : : * the constraint.
412 : : */
9656 413 : 80 : return PointerGetDatum(NULL);
414 : :
415 : : #ifdef NOT_USED
416 : : case FKCONSTR_MATCH_PARTIAL:
417 : :
418 : : /*
419 : : * MATCH PARTIAL - all non-null columns must match. (not
420 : : * implemented, can be done by modifying the query below
421 : : * to only include non-null columns, or by writing a
422 : : * special version here)
423 : : */
424 : : break;
425 : : #endif
426 : : }
427 : :
428 : : case RI_KEYS_NONE_NULL:
429 : :
430 : : /*
431 : : * Have a full qualified key - continue below for all three kinds
432 : : * of MATCH.
433 : : */
9768 JanWieck@Yahoo.com 434 : 605656 : break;
435 : : }
436 : :
437 : : /*
438 : : * Fast path: probe the PK unique index directly, bypassing SPI.
439 : : *
440 : : * For non-partitioned, non-temporal FKs, we can skip the SPI machinery
441 : : * (plan cache, executor setup, etc.) and do a direct index scan + tuple
442 : : * lock. This is semantically equivalent to the SPI path below but avoids
443 : : * the per-row executor overhead.
444 : : *
445 : : * ri_FastPathCheck() reports the violation itself if no matching PK row
446 : : * is found. It returns false if the checks made after opening the
447 : : * relations, on the index and on whether the FK values need a coercion to
448 : : * reach the equality operator's input type, require a SPI fallback.
449 : : */
33 amitlan@postgresql.o 450 [ + + + + ]: 1210070 : if (ri_fastpath_is_applicable(riinfo) &&
451 : 604750 : ri_FastPathCheck(riinfo, fk_rel, newslot))
452 : 604346 : return PointerGetDatum(NULL);
453 : :
787 tgl@sss.pgh.pa.us 454 : 974 : SPI_connect();
455 : :
456 : : /*
457 : : * pk_rel is opened in RowShareLock mode since that's what our eventual
458 : : * SELECT FOR KEY SHARE will get on it.
459 : : */
219 amitlan@postgresql.o 460 : 974 : pk_rel = table_open(riinfo->pk_relid, RowShareLock);
461 : :
462 : : /* Fetch or prepare a saved plan for the real check */
1673 alvherre@alvh.no-ip. 463 : 974 : ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK);
464 : :
465 [ + + ]: 974 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
466 : : {
467 : : StringInfoData querybuf;
468 : : char pkrelname[MAX_QUOTED_REL_NAME_LEN];
469 : : char attname[MAX_QUOTED_NAME_LEN];
470 : : char paramname[16];
471 : : const char *querysep;
472 : : Oid queryoids[RI_MAX_NUMKEYS];
473 : : const char *pk_only;
474 : :
475 : : /* ----------
476 : : * The query string built is
477 : : * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
478 : : * FOR KEY SHARE OF x
479 : : * The type id's for the $ parameters are those of the
480 : : * corresponding FK attributes.
481 : : *
482 : : * But for temporal FKs we need to make sure
483 : : * the FK's range is completely covered.
484 : : * So we use this query instead:
485 : : * SELECT 1
486 : : * FROM (
487 : : * SELECT pkperiodatt AS r
488 : : * FROM [ONLY] pktable x
489 : : * WHERE pkatt1 = $1 [AND ...]
490 : : * AND pkperiodatt && $n
491 : : * FOR KEY SHARE OF x
492 : : * ) x1
493 : : * HAVING $n <@ range_agg(x1.r)
494 : : * Note if FOR KEY SHARE ever allows GROUP BY and HAVING
495 : : * we can make this a bit simpler.
496 : : * ----------
497 : : */
498 : 474 : initStringInfo(&querybuf);
499 : 948 : pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
500 [ + + ]: 474 : "" : "ONLY ";
501 : 474 : quoteRelationName(pkrelname, pk_rel);
779 peter@eisentraut.org 502 [ + + ]: 474 : if (riinfo->hasperiod)
503 : : {
504 : 67 : quoteOneName(attname,
505 : 67 : RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
506 : :
507 : 67 : appendStringInfo(&querybuf,
508 : : "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
509 : : attname, pk_only, pkrelname);
510 : : }
511 : : else
512 : : {
513 : 407 : appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
514 : : pk_only, pkrelname);
515 : : }
1673 alvherre@alvh.no-ip. 516 : 474 : querysep = "WHERE";
517 [ + + ]: 1039 : for (int i = 0; i < riinfo->nkeys; i++)
518 : : {
519 : 565 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
520 : 565 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
521 : :
522 : 565 : quoteOneName(attname,
523 : 565 : RIAttName(pk_rel, riinfo->pk_attnums[i]));
524 : 565 : sprintf(paramname, "$%d", i + 1);
525 : 565 : ri_GenerateQual(&querybuf, querysep,
526 : : attname, pk_type,
527 : : riinfo->pf_eq_oprs[i],
528 : : paramname, fk_type);
529 : 565 : querysep = "AND";
530 : 565 : queryoids[i] = fk_type;
531 : : }
532 : 474 : appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
779 peter@eisentraut.org 533 [ + + ]: 474 : if (riinfo->hasperiod)
534 : : {
535 : 67 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
536 : :
573 drowley@postgresql.o 537 : 67 : appendStringInfoString(&querybuf, ") x1 HAVING ");
779 peter@eisentraut.org 538 : 67 : sprintf(paramname, "$%d", riinfo->nkeys);
539 : 67 : ri_GenerateQual(&querybuf, "",
540 : : paramname, fk_type,
541 : : riinfo->agged_period_contained_by_oper,
542 : : "pg_catalog.range_agg", ANYMULTIRANGEOID);
573 drowley@postgresql.o 543 : 67 : appendStringInfoString(&querybuf, "(x1.r)");
544 : : }
545 : :
546 : : /* Prepare and save the plan */
1673 alvherre@alvh.no-ip. 547 : 474 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
548 : : &qkey, fk_rel, pk_rel);
549 : : }
550 : :
551 : : /*
552 : : * Now check that foreign key exists in PK table
553 : : *
554 : : * XXX detectNewRows must be true when a partitioned table is on the
555 : : * referenced side. The reason is that our snapshot must be fresh in
556 : : * order for the hack in find_inheritance_children() to work.
557 : : */
558 : 974 : ri_PerformCheck(riinfo, &qkey, qplan,
559 : : fk_rel, pk_rel,
560 : : NULL, newslot,
561 : : false,
562 : 974 : pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE,
563 : : SPI_OK_SELECT);
564 : :
565 [ - + ]: 734 : if (SPI_finish() != SPI_OK_FINISH)
1673 alvherre@alvh.no-ip. 566 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
567 : :
2845 andres@anarazel.de 568 :CBC 734 : table_close(pk_rel, RowShareLock);
569 : :
9656 tgl@sss.pgh.pa.us 570 : 734 : return PointerGetDatum(NULL);
571 : : }
572 : :
573 : :
574 : : /*
575 : : * RI_FKey_check_ins -
576 : : *
577 : : * Check foreign key existence at insert event on FK table.
578 : : */
579 : : Datum
580 : 605618 : RI_FKey_check_ins(PG_FUNCTION_ARGS)
581 : : {
582 : : /* Check that this is a valid trigger call on the right time and event. */
5252 583 : 605618 : ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
584 : :
585 : : /* Share code with UPDATE case. */
586 : 605618 : return RI_FKey_check((TriggerData *) fcinfo->context);
587 : : }
588 : :
589 : :
590 : : /*
591 : : * RI_FKey_check_upd -
592 : : *
593 : : * Check foreign key existence at update event on FK table.
594 : : */
595 : : Datum
9656 596 : 296 : RI_FKey_check_upd(PG_FUNCTION_ARGS)
597 : : {
598 : : /* Check that this is a valid trigger call on the right time and event. */
5252 599 : 296 : ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
600 : :
601 : : /* Share code with INSERT case. */
602 : 296 : return RI_FKey_check((TriggerData *) fcinfo->context);
603 : : }
604 : :
605 : :
606 : : /*
607 : : * ri_Check_Pk_Match
608 : : *
609 : : * Check to see if another PK row has been created that provides the same
610 : : * key values as the "oldslot" that's been modified or deleted in our trigger
611 : : * event. Returns true if a match is found in the PK table.
612 : : *
613 : : * We assume the caller checked that the oldslot contains no NULL key values,
614 : : * since otherwise a match is impossible.
615 : : */
616 : : static bool
8636 617 : 530 : ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
618 : : TupleTableSlot *oldslot,
619 : : const RI_ConstraintInfo *riinfo)
620 : : {
621 : : SPIPlanPtr qplan;
622 : : RI_QueryKey qkey;
623 : : bool result;
624 : :
625 : : /* Only called for non-null rows */
2809 andres@anarazel.de 626 [ - + ]: 530 : Assert(ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) == RI_KEYS_NONE_NULL);
627 : :
787 tgl@sss.pgh.pa.us 628 : 530 : SPI_connect();
629 : :
630 : : /*
631 : : * Fetch or prepare a saved plan for checking PK table with values coming
632 : : * from a PK row
633 : : */
1673 alvherre@alvh.no-ip. 634 : 530 : ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CHECK_LOOKUPPK_FROM_PK);
635 : :
636 [ + + ]: 530 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
637 : : {
638 : : StringInfoData querybuf;
639 : : char pkrelname[MAX_QUOTED_REL_NAME_LEN];
640 : : char attname[MAX_QUOTED_NAME_LEN];
641 : : char paramname[16];
642 : : const char *querysep;
643 : : const char *pk_only;
644 : : Oid queryoids[RI_MAX_NUMKEYS];
645 : :
646 : : /* ----------
647 : : * The query string built is
648 : : * SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
649 : : * FOR KEY SHARE OF x
650 : : * The type id's for the $ parameters are those of the
651 : : * PK attributes themselves.
652 : : *
653 : : * But for temporal FKs we need to make sure
654 : : * the old PK's range is completely covered.
655 : : * So we use this query instead:
656 : : * SELECT 1
657 : : * FROM (
658 : : * SELECT pkperiodatt AS r
659 : : * FROM [ONLY] pktable x
660 : : * WHERE pkatt1 = $1 [AND ...]
661 : : * AND pkperiodatt && $n
662 : : * FOR KEY SHARE OF x
663 : : * ) x1
664 : : * HAVING $n <@ range_agg(x1.r)
665 : : * Note if FOR KEY SHARE ever allows GROUP BY and HAVING
666 : : * we can make this a bit simpler.
667 : : * ----------
668 : : */
669 : 244 : initStringInfo(&querybuf);
670 : 488 : pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
671 [ + + ]: 244 : "" : "ONLY ";
672 : 244 : quoteRelationName(pkrelname, pk_rel);
779 peter@eisentraut.org 673 [ - + ]: 244 : if (riinfo->hasperiod)
674 : : {
779 peter@eisentraut.org 675 :UBC 0 : quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
676 : :
677 : 0 : appendStringInfo(&querybuf,
678 : : "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
679 : : attname, pk_only, pkrelname);
680 : : }
681 : : else
682 : : {
779 peter@eisentraut.org 683 :CBC 244 : appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
684 : : pk_only, pkrelname);
685 : : }
1673 alvherre@alvh.no-ip. 686 : 244 : querysep = "WHERE";
687 [ + + ]: 561 : for (int i = 0; i < riinfo->nkeys; i++)
688 : : {
689 : 317 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
690 : :
691 : 317 : quoteOneName(attname,
692 : 317 : RIAttName(pk_rel, riinfo->pk_attnums[i]));
693 : 317 : sprintf(paramname, "$%d", i + 1);
694 : 317 : ri_GenerateQual(&querybuf, querysep,
695 : : attname, pk_type,
696 : 317 : riinfo->pp_eq_oprs[i],
697 : : paramname, pk_type);
698 : 317 : querysep = "AND";
699 : 317 : queryoids[i] = pk_type;
700 : : }
701 : 244 : appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
779 peter@eisentraut.org 702 [ - + ]: 244 : if (riinfo->hasperiod)
703 : : {
779 peter@eisentraut.org 704 :UBC 0 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
705 : :
573 drowley@postgresql.o 706 : 0 : appendStringInfoString(&querybuf, ") x1 HAVING ");
779 peter@eisentraut.org 707 : 0 : sprintf(paramname, "$%d", riinfo->nkeys);
708 : 0 : ri_GenerateQual(&querybuf, "",
709 : : paramname, fk_type,
710 : 0 : riinfo->agged_period_contained_by_oper,
711 : : "pg_catalog.range_agg", ANYMULTIRANGEOID);
573 drowley@postgresql.o 712 : 0 : appendStringInfoString(&querybuf, "(x1.r)");
713 : : }
714 : :
715 : : /* Prepare and save the plan */
1673 alvherre@alvh.no-ip. 716 :CBC 244 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
717 : : &qkey, fk_rel, pk_rel);
718 : : }
719 : :
720 : : /*
721 : : * We have a plan now. Run it.
722 : : */
723 : 530 : result = ri_PerformCheck(riinfo, &qkey, qplan,
724 : : fk_rel, pk_rel,
725 : : oldslot, NULL,
726 : : false,
727 : : true, /* treat like update */
728 : : SPI_OK_SELECT);
729 : :
730 [ - + ]: 530 : if (SPI_finish() != SPI_OK_FINISH)
1673 alvherre@alvh.no-ip. 731 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
732 : :
1673 alvherre@alvh.no-ip. 733 :CBC 530 : return result;
734 : : }
735 : :
736 : :
737 : : /*
738 : : * RI_FKey_noaction_del -
739 : : *
740 : : * Give an error and roll back the current transaction if the
741 : : * delete has resulted in a violation of the given referential
742 : : * integrity constraint.
743 : : */
744 : : Datum
9656 tgl@sss.pgh.pa.us 745 : 301 : RI_FKey_noaction_del(PG_FUNCTION_ARGS)
746 : : {
747 : : /* Check that this is a valid trigger call on the right time and event. */
8636 748 : 301 : ri_CheckTrigger(fcinfo, "RI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
749 : :
750 : : /* Share code with RESTRICT/UPDATE cases. */
3274 751 : 301 : return ri_restrict((TriggerData *) fcinfo->context, true);
752 : : }
753 : :
754 : : /*
755 : : * RI_FKey_restrict_del -
756 : : *
757 : : * Restrict delete from PK table to rows unreferenced by foreign key.
758 : : *
759 : : * The SQL standard intends that this referential action occur exactly when
760 : : * the delete is performed, rather than after. This appears to be
761 : : * the only difference between "NO ACTION" and "RESTRICT". In Postgres
762 : : * we still implement this as an AFTER trigger, but it's non-deferrable.
763 : : */
764 : : Datum
5252 765 : 8 : RI_FKey_restrict_del(PG_FUNCTION_ARGS)
766 : : {
767 : : /* Check that this is a valid trigger call on the right time and event. */
768 : 8 : ri_CheckTrigger(fcinfo, "RI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
769 : :
770 : : /* Share code with NO ACTION/UPDATE cases. */
3274 771 : 8 : return ri_restrict((TriggerData *) fcinfo->context, false);
772 : : }
773 : :
774 : : /*
775 : : * RI_FKey_noaction_upd -
776 : : *
777 : : * Give an error and roll back the current transaction if the
778 : : * update has resulted in a violation of the given referential
779 : : * integrity constraint.
780 : : */
781 : : Datum
5252 782 : 351 : RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
783 : : {
784 : : /* Check that this is a valid trigger call on the right time and event. */
785 : 351 : ri_CheckTrigger(fcinfo, "RI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
786 : :
787 : : /* Share code with RESTRICT/DELETE cases. */
3274 788 : 351 : return ri_restrict((TriggerData *) fcinfo->context, true);
789 : : }
790 : :
791 : : /*
792 : : * RI_FKey_restrict_upd -
793 : : *
794 : : * Restrict update of PK to rows unreferenced by foreign key.
795 : : *
796 : : * The SQL standard intends that this referential action occur exactly when
797 : : * the update is performed, rather than after. This appears to be
798 : : * the only difference between "NO ACTION" and "RESTRICT". In Postgres
799 : : * we still implement this as an AFTER trigger, but it's non-deferrable.
800 : : */
801 : : Datum
5252 802 : 20 : RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
803 : : {
804 : : /* Check that this is a valid trigger call on the right time and event. */
805 : 20 : ri_CheckTrigger(fcinfo, "RI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
806 : :
807 : : /* Share code with NO ACTION/DELETE cases. */
3274 808 : 20 : return ri_restrict((TriggerData *) fcinfo->context, false);
809 : : }
810 : :
811 : : /*
812 : : * ri_restrict -
813 : : *
814 : : * Common code for ON DELETE RESTRICT, ON DELETE NO ACTION,
815 : : * ON UPDATE RESTRICT, and ON UPDATE NO ACTION.
816 : : */
817 : : static Datum
818 : 768 : ri_restrict(TriggerData *trigdata, bool is_no_action)
819 : : {
820 : : const RI_ConstraintInfo *riinfo;
821 : : Relation fk_rel;
822 : : Relation pk_rel;
823 : : TupleTableSlot *oldslot;
824 : : RI_QueryKey qkey;
825 : : SPIPlanPtr qplan;
826 : :
5251 827 : 768 : riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
828 : : trigdata->tg_relation, true);
829 : :
830 : : /*
831 : : * Get the relation descriptors of the FK and PK tables and the old tuple.
832 : : *
833 : : * fk_rel is opened in RowShareLock mode since that's what our eventual
834 : : * SELECT FOR KEY SHARE will get on it.
835 : : */
2845 andres@anarazel.de 836 : 768 : fk_rel = table_open(riinfo->fk_relid, RowShareLock);
9703 bruce@momjian.us 837 : 768 : pk_rel = trigdata->tg_relation;
2807 peter@eisentraut.org 838 : 768 : oldslot = trigdata->tg_trigslot;
839 : :
840 : : /*
841 : : * If another PK row now exists providing the old key values, we should
842 : : * not do anything. However, this check should only be made in the NO
843 : : * ACTION case; in RESTRICT cases we don't wish to allow another row to be
844 : : * substituted.
845 : : *
846 : : * If the foreign key has PERIOD, we incorporate looking for replacement
847 : : * rows in the main SQL query below, so we needn't do it here.
848 : : */
653 849 [ + + + + : 1298 : if (is_no_action && !riinfo->hasperiod &&
+ + ]
2807 850 : 530 : ri_Check_Pk_Match(pk_rel, fk_rel, oldslot, riinfo))
851 : : {
852 : 38 : table_close(fk_rel, RowShareLock);
853 : 38 : return PointerGetDatum(NULL);
854 : : }
855 : :
787 tgl@sss.pgh.pa.us 856 : 730 : SPI_connect();
857 : :
858 : : /*
859 : : * Fetch or prepare a saved plan for the restrict lookup (it's the same
860 : : * query for delete and update cases)
861 : : */
634 peter@eisentraut.org 862 [ + + ]: 730 : ri_BuildQueryKey(&qkey, riinfo, is_no_action ? RI_PLAN_NO_ACTION : RI_PLAN_RESTRICT);
863 : :
2807 864 [ + + ]: 730 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
865 : : {
866 : : StringInfoData querybuf;
867 : : char pkrelname[MAX_QUOTED_REL_NAME_LEN];
868 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
869 : : char attname[MAX_QUOTED_NAME_LEN];
870 : : char periodattname[MAX_QUOTED_NAME_LEN];
871 : : char paramname[16];
872 : : const char *querysep;
873 : : Oid queryoids[RI_MAX_NUMKEYS];
874 : : const char *fk_only;
875 : :
876 : : /* ----------
877 : : * The query string built is
878 : : * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = fkatt1 [AND ...]
879 : : * FOR KEY SHARE OF x
880 : : * The type id's for the $ parameters are those of the
881 : : * corresponding PK attributes.
882 : : * ----------
883 : : */
884 : 304 : initStringInfo(&querybuf);
885 : 608 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
886 [ + + ]: 304 : "" : "ONLY ";
887 : 304 : quoteRelationName(fkrelname, fk_rel);
888 : 304 : appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
889 : : fk_only, fkrelname);
890 : 304 : querysep = "WHERE";
891 [ + + ]: 767 : for (int i = 0; i < riinfo->nkeys; i++)
892 : : {
893 : 463 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
894 : 463 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
895 : :
896 : 463 : quoteOneName(attname,
897 : 463 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
898 : 463 : sprintf(paramname, "$%d", i + 1);
899 : 463 : ri_GenerateQual(&querybuf, querysep,
900 : : paramname, pk_type,
901 : 463 : riinfo->pf_eq_oprs[i],
902 : : attname, fk_type);
903 : 463 : querysep = "AND";
904 : 463 : queryoids[i] = pk_type;
905 : : }
906 : :
907 : : /*----------
908 : : * For temporal foreign keys, a reference could still be valid if the
909 : : * referenced range didn't change too much. Also if a referencing
910 : : * range extends past the current PK row, we don't want to check that
911 : : * part: some other PK row should fulfill it. We only want to check
912 : : * the part matching the PK record we've changed. Therefore to find
913 : : * invalid records we do this:
914 : : *
915 : : * SELECT 1 FROM [ONLY] <fktable> x WHERE $1 = x.fkatt1 [AND ...]
916 : : * -- begin temporal
917 : : * AND $n && x.fkperiod
918 : : * AND NOT coalesce((x.fkperiod * $n) <@
919 : : * (SELECT range_agg(r)
920 : : * FROM (SELECT y.pkperiod r
921 : : * FROM [ONLY] <pktable> y
922 : : * WHERE $1 = y.pkatt1 [AND ...] AND $n && y.pkperiod
923 : : * FOR KEY SHARE OF y) y2), false)
924 : : * -- end temporal
925 : : * FOR KEY SHARE OF x
926 : : *
927 : : * We need the coalesce in case the first subquery returns no rows.
928 : : * We need the second subquery because FOR KEY SHARE doesn't support
929 : : * aggregate queries.
930 : : */
653 931 [ + + + - ]: 304 : if (riinfo->hasperiod && is_no_action)
932 : : {
933 : 86 : Oid pk_period_type = RIAttType(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
934 : 86 : Oid fk_period_type = RIAttType(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
935 : : StringInfoData intersectbuf;
936 : : StringInfoData replacementsbuf;
937 : 172 : char *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
938 [ - + ]: 86 : "" : "ONLY ";
939 : :
940 : 86 : quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
941 : 86 : sprintf(paramname, "$%d", riinfo->nkeys);
942 : :
943 : 86 : appendStringInfoString(&querybuf, " AND NOT coalesce(");
944 : :
945 : : /* Intersect the fk with the old pk range */
946 : 86 : initStringInfo(&intersectbuf);
573 drowley@postgresql.o 947 : 86 : appendStringInfoChar(&intersectbuf, '(');
653 peter@eisentraut.org 948 : 86 : ri_GenerateQual(&intersectbuf, "",
949 : : attname, fk_period_type,
950 : 86 : riinfo->period_intersect_oper,
951 : : paramname, pk_period_type);
573 drowley@postgresql.o 952 : 86 : appendStringInfoChar(&intersectbuf, ')');
953 : :
954 : : /* Find the remaining history */
653 peter@eisentraut.org 955 : 86 : initStringInfo(&replacementsbuf);
956 : 86 : appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
957 : :
958 : 86 : quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
959 : 86 : quoteRelationName(pkrelname, pk_rel);
960 : 86 : appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
961 : : periodattname, pk_only, pkrelname);
962 : :
963 : : /* Restrict pk rows to what matches */
964 : 86 : querysep = "WHERE";
965 [ + + ]: 258 : for (int i = 0; i < riinfo->nkeys; i++)
966 : : {
967 : 172 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
968 : :
969 : 172 : quoteOneName(attname,
970 : 172 : RIAttName(pk_rel, riinfo->pk_attnums[i]));
971 : 172 : sprintf(paramname, "$%d", i + 1);
972 : 172 : ri_GenerateQual(&replacementsbuf, querysep,
973 : : paramname, pk_type,
974 : 172 : riinfo->pp_eq_oprs[i],
975 : : attname, pk_type);
976 : 172 : querysep = "AND";
977 : 172 : queryoids[i] = pk_type;
978 : : }
979 : 86 : appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
980 : :
981 : 86 : ri_GenerateQual(&querybuf, "",
982 : 86 : intersectbuf.data, fk_period_type,
983 : 86 : riinfo->agged_period_contained_by_oper,
984 : 86 : replacementsbuf.data, ANYMULTIRANGEOID);
985 : : /* end of coalesce: */
986 : 86 : appendStringInfoString(&querybuf, ", false)");
987 : : }
988 : :
2807 989 : 304 : appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
990 : :
991 : : /* Prepare and save the plan */
992 : 304 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
993 : : &qkey, fk_rel, pk_rel);
994 : : }
995 : :
996 : : /*
997 : : * We have a plan now. Run it to check for existing references.
998 : : */
999 : 730 : ri_PerformCheck(riinfo, &qkey, qplan,
1000 : : fk_rel, pk_rel,
1001 : : oldslot, NULL,
1002 : : !is_no_action,
1003 : : true, /* must detect new rows */
1004 : 730 : SPI_OK_SELECT);
1005 : :
1006 [ - + ]: 416 : if (SPI_finish() != SPI_OK_FINISH)
2807 peter@eisentraut.org 1007 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
1008 : :
2807 peter@eisentraut.org 1009 :CBC 416 : table_close(fk_rel, RowShareLock);
1010 : :
9656 tgl@sss.pgh.pa.us 1011 : 416 : return PointerGetDatum(NULL);
1012 : : }
1013 : :
1014 : :
1015 : : /*
1016 : : * RI_FKey_cascade_del -
1017 : : *
1018 : : * Cascaded delete foreign key references at delete event on PK table.
1019 : : */
1020 : : Datum
5252 1021 : 218 : RI_FKey_cascade_del(PG_FUNCTION_ARGS)
1022 : : {
9656 1023 : 218 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
1024 : : const RI_ConstraintInfo *riinfo;
1025 : : Relation fk_rel;
1026 : : Relation pk_rel;
1027 : : TupleTableSlot *oldslot;
1028 : : RI_QueryKey qkey;
1029 : : SPIPlanPtr qplan;
1030 : :
1031 : : /* Check that this is a valid trigger call on the right time and event. */
5252 1032 : 218 : ri_CheckTrigger(fcinfo, "RI_FKey_cascade_del", RI_TRIGTYPE_DELETE);
1033 : :
5251 1034 : 218 : riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
1035 : : trigdata->tg_relation, true);
1036 : :
1037 : : /*
1038 : : * Get the relation descriptors of the FK and PK tables and the old tuple.
1039 : : *
1040 : : * fk_rel is opened in RowExclusiveLock mode since that's what our
1041 : : * eventual DELETE will get on it.
1042 : : */
2845 andres@anarazel.de 1043 : 218 : fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
9703 bruce@momjian.us 1044 : 218 : pk_rel = trigdata->tg_relation;
2807 peter@eisentraut.org 1045 : 218 : oldslot = trigdata->tg_trigslot;
1046 : :
787 tgl@sss.pgh.pa.us 1047 : 218 : SPI_connect();
1048 : :
1049 : : /* Fetch or prepare a saved plan for the cascaded delete */
1793 peter@eisentraut.org 1050 : 218 : ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONDELETE);
1051 : :
2807 1052 [ + + ]: 218 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
1053 : : {
1054 : : StringInfoData querybuf;
1055 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
1056 : : char attname[MAX_QUOTED_NAME_LEN];
1057 : : char paramname[16];
1058 : : const char *querysep;
1059 : : Oid queryoids[RI_MAX_NUMKEYS];
1060 : : const char *fk_only;
1061 : :
1062 : : /* ----------
1063 : : * The query string built is
1064 : : * DELETE FROM [ONLY] <fktable> WHERE $1 = fkatt1 [AND ...]
1065 : : * The type id's for the $ parameters are those of the
1066 : : * corresponding PK attributes.
1067 : : * ----------
1068 : : */
1069 : 154 : initStringInfo(&querybuf);
1070 : 308 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1071 [ + + ]: 154 : "" : "ONLY ";
1072 : 154 : quoteRelationName(fkrelname, fk_rel);
1073 : 154 : appendStringInfo(&querybuf, "DELETE FROM %s%s",
1074 : : fk_only, fkrelname);
1075 : 154 : querysep = "WHERE";
1076 [ + + ]: 320 : for (int i = 0; i < riinfo->nkeys; i++)
1077 : : {
1078 : 166 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
1079 : 166 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
1080 : :
1081 : 166 : quoteOneName(attname,
1082 : 166 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
1083 : 166 : sprintf(paramname, "$%d", i + 1);
1084 : 166 : ri_GenerateQual(&querybuf, querysep,
1085 : : paramname, pk_type,
1086 : 166 : riinfo->pf_eq_oprs[i],
1087 : : attname, fk_type);
1088 : 166 : querysep = "AND";
1089 : 166 : queryoids[i] = pk_type;
1090 : : }
1091 : :
1092 : : /* Prepare and save the plan */
1093 : 154 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
1094 : : &qkey, fk_rel, pk_rel);
1095 : : }
1096 : :
1097 : : /*
1098 : : * We have a plan now. Build up the arguments from the key values in the
1099 : : * deleted PK tuple and delete the referencing rows
1100 : : */
1101 : 218 : ri_PerformCheck(riinfo, &qkey, qplan,
1102 : : fk_rel, pk_rel,
1103 : : oldslot, NULL,
1104 : : false,
1105 : : true, /* must detect new rows */
1106 : : SPI_OK_DELETE);
1107 : :
1108 [ - + ]: 137 : if (SPI_finish() != SPI_OK_FINISH)
2807 peter@eisentraut.org 1109 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
1110 : :
2807 peter@eisentraut.org 1111 :CBC 137 : table_close(fk_rel, RowExclusiveLock);
1112 : :
9656 tgl@sss.pgh.pa.us 1113 : 137 : return PointerGetDatum(NULL);
1114 : : }
1115 : :
1116 : :
1117 : : /*
1118 : : * RI_FKey_cascade_upd -
1119 : : *
1120 : : * Cascaded update foreign key references at update event on PK table.
1121 : : */
1122 : : Datum
5252 1123 : 144 : RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
1124 : : {
9656 1125 : 144 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
1126 : : const RI_ConstraintInfo *riinfo;
1127 : : Relation fk_rel;
1128 : : Relation pk_rel;
1129 : : TupleTableSlot *newslot;
1130 : : TupleTableSlot *oldslot;
1131 : : RI_QueryKey qkey;
1132 : : SPIPlanPtr qplan;
1133 : :
1134 : : /* Check that this is a valid trigger call on the right time and event. */
5252 1135 : 144 : ri_CheckTrigger(fcinfo, "RI_FKey_cascade_upd", RI_TRIGTYPE_UPDATE);
1136 : :
5251 1137 : 144 : riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
1138 : : trigdata->tg_relation, true);
1139 : :
1140 : : /*
1141 : : * Get the relation descriptors of the FK and PK tables and the new and
1142 : : * old tuple.
1143 : : *
1144 : : * fk_rel is opened in RowExclusiveLock mode since that's what our
1145 : : * eventual UPDATE will get on it.
1146 : : */
2845 andres@anarazel.de 1147 : 144 : fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
9703 bruce@momjian.us 1148 : 144 : pk_rel = trigdata->tg_relation;
2807 peter@eisentraut.org 1149 : 144 : newslot = trigdata->tg_newslot;
1150 : 144 : oldslot = trigdata->tg_trigslot;
1151 : :
787 tgl@sss.pgh.pa.us 1152 : 144 : SPI_connect();
1153 : :
1154 : : /* Fetch or prepare a saved plan for the cascaded update */
1793 peter@eisentraut.org 1155 : 144 : ri_BuildQueryKey(&qkey, riinfo, RI_PLAN_CASCADE_ONUPDATE);
1156 : :
2807 1157 [ + + ]: 144 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
1158 : : {
1159 : : StringInfoData querybuf;
1160 : : StringInfoData qualbuf;
1161 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
1162 : : char attname[MAX_QUOTED_NAME_LEN];
1163 : : char paramname[16];
1164 : : const char *querysep;
1165 : : const char *qualsep;
1166 : : Oid queryoids[RI_MAX_NUMKEYS * 2];
1167 : : const char *fk_only;
1168 : :
1169 : : /* ----------
1170 : : * The query string built is
1171 : : * UPDATE [ONLY] <fktable> SET fkatt1 = $1 [, ...]
1172 : : * WHERE $n = fkatt1 [AND ...]
1173 : : * The type id's for the $ parameters are those of the
1174 : : * corresponding PK attributes. Note that we are assuming
1175 : : * there is an assignment cast from the PK to the FK type;
1176 : : * else the parser will fail.
1177 : : * ----------
1178 : : */
1179 : 84 : initStringInfo(&querybuf);
1180 : 84 : initStringInfo(&qualbuf);
1181 : 168 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1182 [ + + ]: 84 : "" : "ONLY ";
1183 : 84 : quoteRelationName(fkrelname, fk_rel);
1184 : 84 : appendStringInfo(&querybuf, "UPDATE %s%s SET",
1185 : : fk_only, fkrelname);
1186 : 84 : querysep = "";
1187 : 84 : qualsep = "WHERE";
1188 [ + + ]: 184 : for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
1189 : : {
1190 : 100 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
1191 : 100 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
1192 : :
1193 : 100 : quoteOneName(attname,
1194 : 100 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
1195 : 100 : appendStringInfo(&querybuf,
1196 : : "%s %s = $%d",
1197 : : querysep, attname, i + 1);
1198 : 100 : sprintf(paramname, "$%d", j + 1);
1199 : 100 : ri_GenerateQual(&qualbuf, qualsep,
1200 : : paramname, pk_type,
1201 : 100 : riinfo->pf_eq_oprs[i],
1202 : : attname, fk_type);
1203 : 100 : querysep = ",";
1204 : 100 : qualsep = "AND";
1205 : 100 : queryoids[i] = pk_type;
1206 : 100 : queryoids[j] = pk_type;
1207 : : }
2662 drowley@postgresql.o 1208 : 84 : appendBinaryStringInfo(&querybuf, qualbuf.data, qualbuf.len);
1209 : :
1210 : : /* Prepare and save the plan */
2807 peter@eisentraut.org 1211 : 84 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys * 2, queryoids,
1212 : : &qkey, fk_rel, pk_rel);
1213 : : }
1214 : :
1215 : : /*
1216 : : * We have a plan now. Run it to update the existing references.
1217 : : */
1218 : 144 : ri_PerformCheck(riinfo, &qkey, qplan,
1219 : : fk_rel, pk_rel,
1220 : : oldslot, newslot,
1221 : : false,
1222 : : true, /* must detect new rows */
1223 : : SPI_OK_UPDATE);
1224 : :
1225 [ - + ]: 144 : if (SPI_finish() != SPI_OK_FINISH)
2807 peter@eisentraut.org 1226 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
1227 : :
2807 peter@eisentraut.org 1228 :CBC 144 : table_close(fk_rel, RowExclusiveLock);
1229 : :
9656 tgl@sss.pgh.pa.us 1230 : 144 : return PointerGetDatum(NULL);
1231 : : }
1232 : :
1233 : :
1234 : : /*
1235 : : * RI_FKey_setnull_del -
1236 : : *
1237 : : * Set foreign key references to NULL values at delete event on PK table.
1238 : : */
1239 : : Datum
1240 : 65 : RI_FKey_setnull_del(PG_FUNCTION_ARGS)
1241 : : {
1242 : : /* Check that this is a valid trigger call on the right time and event. */
3274 1243 : 65 : ri_CheckTrigger(fcinfo, "RI_FKey_setnull_del", RI_TRIGTYPE_DELETE);
1244 : :
1245 : : /* Share code with UPDATE case */
1793 peter@eisentraut.org 1246 : 65 : return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_DELETE);
1247 : : }
1248 : :
1249 : : /*
1250 : : * RI_FKey_setnull_upd -
1251 : : *
1252 : : * Set foreign key references to NULL at update event on PK table.
1253 : : */
1254 : : Datum
3274 tgl@sss.pgh.pa.us 1255 : 20 : RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
1256 : : {
1257 : : /* Check that this is a valid trigger call on the right time and event. */
1258 : 20 : ri_CheckTrigger(fcinfo, "RI_FKey_setnull_upd", RI_TRIGTYPE_UPDATE);
1259 : :
1260 : : /* Share code with DELETE case */
1793 peter@eisentraut.org 1261 : 20 : return ri_set((TriggerData *) fcinfo->context, true, RI_TRIGTYPE_UPDATE);
1262 : : }
1263 : :
1264 : : /*
1265 : : * RI_FKey_setdefault_del -
1266 : : *
1267 : : * Set foreign key references to defaults at delete event on PK table.
1268 : : */
1269 : : Datum
3274 tgl@sss.pgh.pa.us 1270 : 56 : RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
1271 : : {
1272 : : /* Check that this is a valid trigger call on the right time and event. */
1273 : 56 : ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_del", RI_TRIGTYPE_DELETE);
1274 : :
1275 : : /* Share code with UPDATE case */
1793 peter@eisentraut.org 1276 : 56 : return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_DELETE);
1277 : : }
1278 : :
1279 : : /*
1280 : : * RI_FKey_setdefault_upd -
1281 : : *
1282 : : * Set foreign key references to defaults at update event on PK table.
1283 : : */
1284 : : Datum
3274 tgl@sss.pgh.pa.us 1285 : 32 : RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
1286 : : {
1287 : : /* Check that this is a valid trigger call on the right time and event. */
1288 : 32 : ri_CheckTrigger(fcinfo, "RI_FKey_setdefault_upd", RI_TRIGTYPE_UPDATE);
1289 : :
1290 : : /* Share code with DELETE case */
1793 peter@eisentraut.org 1291 : 32 : return ri_set((TriggerData *) fcinfo->context, false, RI_TRIGTYPE_UPDATE);
1292 : : }
1293 : :
1294 : : /*
1295 : : * ri_set -
1296 : : *
1297 : : * Common code for ON DELETE SET NULL, ON DELETE SET DEFAULT, ON UPDATE SET
1298 : : * NULL, and ON UPDATE SET DEFAULT.
1299 : : */
1300 : : static Datum
1301 : 173 : ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
1302 : : {
1303 : : const RI_ConstraintInfo *riinfo;
1304 : : Relation fk_rel;
1305 : : Relation pk_rel;
1306 : : TupleTableSlot *oldslot;
1307 : : RI_QueryKey qkey;
1308 : : SPIPlanPtr qplan;
1309 : : int32 queryno;
1310 : :
5251 tgl@sss.pgh.pa.us 1311 : 173 : riinfo = ri_FetchConstraintInfo(trigdata->tg_trigger,
1312 : : trigdata->tg_relation, true);
1313 : :
1314 : : /*
1315 : : * Get the relation descriptors of the FK and PK tables and the old tuple.
1316 : : *
1317 : : * fk_rel is opened in RowExclusiveLock mode since that's what our
1318 : : * eventual UPDATE will get on it.
1319 : : */
2845 andres@anarazel.de 1320 : 173 : fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock);
9703 bruce@momjian.us 1321 : 173 : pk_rel = trigdata->tg_relation;
2807 peter@eisentraut.org 1322 : 173 : oldslot = trigdata->tg_trigslot;
1323 : :
787 tgl@sss.pgh.pa.us 1324 : 173 : SPI_connect();
1325 : :
1326 : : /*
1327 : : * Fetch or prepare a saved plan for the trigger.
1328 : : */
1638 1329 [ + + - ]: 173 : switch (tgkind)
1330 : : {
1793 peter@eisentraut.org 1331 : 52 : case RI_TRIGTYPE_UPDATE:
1332 : 52 : queryno = is_set_null
1333 : : ? RI_PLAN_SETNULL_ONUPDATE
1334 [ + + ]: 52 : : RI_PLAN_SETDEFAULT_ONUPDATE;
1335 : 52 : break;
1336 : 121 : case RI_TRIGTYPE_DELETE:
1337 : 121 : queryno = is_set_null
1338 : : ? RI_PLAN_SETNULL_ONDELETE
1339 [ + + ]: 121 : : RI_PLAN_SETDEFAULT_ONDELETE;
1340 : 121 : break;
1793 peter@eisentraut.org 1341 :UBC 0 : default:
1342 [ # # ]: 0 : elog(ERROR, "invalid tgkind passed to ri_set");
1343 : : }
1344 : :
1793 peter@eisentraut.org 1345 :CBC 173 : ri_BuildQueryKey(&qkey, riinfo, queryno);
1346 : :
2807 1347 [ + + ]: 173 : if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
1348 : : {
1349 : : StringInfoData querybuf;
1350 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
1351 : : char attname[MAX_QUOTED_NAME_LEN];
1352 : : char paramname[16];
1353 : : const char *querysep;
1354 : : const char *qualsep;
1355 : : Oid queryoids[RI_MAX_NUMKEYS];
1356 : : const char *fk_only;
1357 : : int num_cols_to_set;
1358 : : const int16 *set_cols;
1359 : :
1638 tgl@sss.pgh.pa.us 1360 [ + + - ]: 100 : switch (tgkind)
1361 : : {
1793 peter@eisentraut.org 1362 : 32 : case RI_TRIGTYPE_UPDATE:
1363 : 32 : num_cols_to_set = riinfo->nkeys;
1364 : 32 : set_cols = riinfo->fk_attnums;
1365 : 32 : break;
1366 : 68 : case RI_TRIGTYPE_DELETE:
1367 : :
1368 : : /*
1369 : : * If confdelsetcols are present, then we only update the
1370 : : * columns specified in that array, otherwise we update all
1371 : : * the referencing columns.
1372 : : */
1638 tgl@sss.pgh.pa.us 1373 [ + + ]: 68 : if (riinfo->ndelsetcols != 0)
1374 : : {
1793 peter@eisentraut.org 1375 : 16 : num_cols_to_set = riinfo->ndelsetcols;
1376 : 16 : set_cols = riinfo->confdelsetcols;
1377 : : }
1378 : : else
1379 : : {
1380 : 52 : num_cols_to_set = riinfo->nkeys;
1381 : 52 : set_cols = riinfo->fk_attnums;
1382 : : }
1383 : 68 : break;
1793 peter@eisentraut.org 1384 :UBC 0 : default:
1385 [ # # ]: 0 : elog(ERROR, "invalid tgkind passed to ri_set");
1386 : : }
1387 : :
1388 : : /* ----------
1389 : : * The query string built is
1390 : : * UPDATE [ONLY] <fktable> SET fkatt1 = {NULL|DEFAULT} [, ...]
1391 : : * WHERE $1 = fkatt1 [AND ...]
1392 : : * The type id's for the $ parameters are those of the
1393 : : * corresponding PK attributes.
1394 : : * ----------
1395 : : */
2807 peter@eisentraut.org 1396 :CBC 100 : initStringInfo(&querybuf);
1397 : 200 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1398 [ + + ]: 100 : "" : "ONLY ";
1399 : 100 : quoteRelationName(fkrelname, fk_rel);
1400 : 100 : appendStringInfo(&querybuf, "UPDATE %s%s SET",
1401 : : fk_only, fkrelname);
1402 : :
1403 : : /*
1404 : : * Add assignment clauses
1405 : : */
1406 : 100 : querysep = "";
1793 1407 [ + + ]: 263 : for (int i = 0; i < num_cols_to_set; i++)
1408 : : {
1409 : 163 : quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
1410 [ + + ]: 163 : appendStringInfo(&querybuf,
1411 : : "%s %s = %s",
1412 : : querysep, attname,
1413 : : is_set_null ? "NULL" : "DEFAULT");
1414 : 163 : querysep = ",";
1415 : : }
1416 : :
1417 : : /*
1418 : : * Add WHERE clause
1419 : : */
2807 1420 : 100 : qualsep = "WHERE";
1421 [ + + ]: 279 : for (int i = 0; i < riinfo->nkeys; i++)
1422 : : {
1423 : 179 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
1424 : 179 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
1425 : :
1426 : 179 : quoteOneName(attname,
1427 : 179 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
1428 : :
1429 : 179 : sprintf(paramname, "$%d", i + 1);
1793 1430 : 179 : ri_GenerateQual(&querybuf, qualsep,
1431 : : paramname, pk_type,
2807 1432 : 179 : riinfo->pf_eq_oprs[i],
1433 : : attname, fk_type);
1434 : 179 : qualsep = "AND";
1435 : 179 : queryoids[i] = pk_type;
1436 : : }
1437 : :
1438 : : /* Prepare and save the plan */
1439 : 100 : qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
1440 : : &qkey, fk_rel, pk_rel);
1441 : : }
1442 : :
1443 : : /*
1444 : : * We have a plan now. Run it to update the existing references.
1445 : : */
1446 : 173 : ri_PerformCheck(riinfo, &qkey, qplan,
1447 : : fk_rel, pk_rel,
1448 : : oldslot, NULL,
1449 : : false,
1450 : : true, /* must detect new rows */
1451 : : SPI_OK_UPDATE);
1452 : :
1453 [ - + ]: 172 : if (SPI_finish() != SPI_OK_FINISH)
2807 peter@eisentraut.org 1454 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
1455 : :
2807 peter@eisentraut.org 1456 :CBC 172 : table_close(fk_rel, RowExclusiveLock);
1457 : :
1458 [ + + ]: 172 : if (is_set_null)
1459 : 84 : return PointerGetDatum(NULL);
1460 : : else
1461 : : {
1462 : : /*
1463 : : * If we just deleted or updated the PK row whose key was equal to the
1464 : : * FK columns' default values, and a referencing row exists in the FK
1465 : : * table, we would have updated that row to the same values it already
1466 : : * had --- and RI_FKey_fk_upd_check_required would hence believe no
1467 : : * check is necessary. So we need to do another lookup now and in
1468 : : * case a reference still exists, abort the operation. That is
1469 : : * already implemented in the NO ACTION trigger, so just run it. (This
1470 : : * recheck is only needed in the SET DEFAULT case, since CASCADE would
1471 : : * remove such rows in case of a DELETE operation or would change the
1472 : : * FK key values in case of an UPDATE, while SET NULL is certain to
1473 : : * result in rows that satisfy the FK constraint.)
1474 : : */
1475 : 88 : return ri_restrict(trigdata, true);
1476 : : }
1477 : : }
1478 : :
1479 : :
1480 : : /*
1481 : : * RI_FKey_pk_upd_check_required -
1482 : : *
1483 : : * Check if we really need to fire the RI trigger for an update or delete to a PK
1484 : : * relation. This is called by the AFTER trigger queue manager to see if
1485 : : * it can skip queuing an instance of an RI trigger. Returns true if the
1486 : : * trigger must be fired, false if we can prove the constraint will still
1487 : : * be satisfied.
1488 : : *
1489 : : * newslot will be NULL if this is called for a delete.
1490 : : */
1491 : : bool
5252 tgl@sss.pgh.pa.us 1492 : 1640 : RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
1493 : : TupleTableSlot *oldslot, TupleTableSlot *newslot)
1494 : : {
1495 : : const RI_ConstraintInfo *riinfo;
1496 : :
5251 1497 : 1640 : riinfo = ri_FetchConstraintInfo(trigger, pk_rel, true);
1498 : :
1499 : : /*
1500 : : * If any old key value is NULL, the row could not have been referenced by
1501 : : * an FK row, so no check is needed.
1502 : : */
2807 peter@eisentraut.org 1503 [ + + ]: 1640 : if (ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) != RI_KEYS_NONE_NULL)
1504 : 4 : return false;
1505 : :
1506 : : /* If all old and new key values are equal, no check is needed */
1507 [ + + + + ]: 1636 : if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
1508 : 292 : return false;
1509 : :
1510 : : /* Else we need to fire the trigger. */
1511 : 1344 : return true;
1512 : : }
1513 : :
1514 : : /*
1515 : : * RI_FKey_fk_upd_check_required -
1516 : : *
1517 : : * Check if we really need to fire the RI trigger for an update to an FK
1518 : : * relation. This is called by the AFTER trigger queue manager to see if
1519 : : * it can skip queuing an instance of an RI trigger. Returns true if the
1520 : : * trigger must be fired, false if we can prove the constraint will still
1521 : : * be satisfied.
1522 : : */
1523 : : bool
5252 tgl@sss.pgh.pa.us 1524 : 670 : RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
1525 : : TupleTableSlot *oldslot, TupleTableSlot *newslot)
1526 : : {
1527 : : const RI_ConstraintInfo *riinfo;
1528 : : int ri_nullcheck;
1529 : :
1530 : : /*
1531 : : * AfterTriggerSaveEvent() handles things such that this function is never
1532 : : * called for partitioned tables.
1533 : : */
1691 alvherre@alvh.no-ip. 1534 [ - + ]: 670 : Assert(fk_rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE);
1535 : :
5251 tgl@sss.pgh.pa.us 1536 : 670 : riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
1537 : :
2807 peter@eisentraut.org 1538 : 670 : ri_nullcheck = ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false);
1539 : :
1540 : : /*
1541 : : * If all new key values are NULL, the row satisfies the constraint, so no
1542 : : * check is needed.
1543 : : */
1544 [ + + ]: 670 : if (ri_nullcheck == RI_KEYS_ALL_NULL)
1545 : 84 : return false;
1546 : :
1547 : : /*
1548 : : * If some new key values are NULL, the behavior depends on the match
1549 : : * type.
1550 : : */
1551 [ + + ]: 586 : else if (ri_nullcheck == RI_KEYS_SOME_NULL)
1552 : : {
1553 [ + - + - ]: 20 : switch (riinfo->confmatchtype)
1554 : : {
1555 : 16 : case FKCONSTR_MATCH_SIMPLE:
1556 : :
1557 : : /*
1558 : : * If any new key value is NULL, the row must satisfy the
1559 : : * constraint, so no check is needed.
1560 : : */
5252 tgl@sss.pgh.pa.us 1561 : 16 : return false;
1562 : :
2807 peter@eisentraut.org 1563 :UBC 0 : case FKCONSTR_MATCH_PARTIAL:
1564 : :
1565 : : /*
1566 : : * Don't know, must run full check.
1567 : : */
1568 : 0 : break;
1569 : :
2807 peter@eisentraut.org 1570 :CBC 4 : case FKCONSTR_MATCH_FULL:
1571 : :
1572 : : /*
1573 : : * If some new key values are NULL, the row fails the
1574 : : * constraint. We must not throw error here, because the row
1575 : : * might get invalidated before the constraint is to be
1576 : : * checked, but we should queue the event to apply the check
1577 : : * later.
1578 : : */
5252 tgl@sss.pgh.pa.us 1579 : 4 : return true;
1580 : : }
1581 : : }
1582 : :
1583 : : /*
1584 : : * Continues here for no new key values are NULL, or we couldn't decide
1585 : : * yet.
1586 : : */
1587 : :
1588 : : /*
1589 : : * If the original row was inserted by our own transaction, we must fire
1590 : : * the trigger whether or not the keys are equal. This is because our
1591 : : * UPDATE will invalidate the INSERT so that the INSERT RI trigger will
1592 : : * not do anything; so we had better do the UPDATE check. (We could skip
1593 : : * this if we knew the INSERT trigger already fired, but there is no easy
1594 : : * way to know that.)
1595 : : */
959 akorotkov@postgresql 1596 [ + + ]: 566 : if (slot_is_current_xact_tuple(oldslot))
2807 peter@eisentraut.org 1597 : 77 : return true;
1598 : :
1599 : : /* If all old and new key values are equal, no check is needed */
1600 [ + + ]: 489 : if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
1601 : 262 : return false;
1602 : :
1603 : : /* Else we need to fire the trigger. */
1604 : 227 : return true;
1605 : : }
1606 : :
1607 : : /*
1608 : : * RI_Initial_Check -
1609 : : *
1610 : : * Check an entire table for non-matching values using a single query.
1611 : : * This is not a trigger procedure, but is called during ALTER TABLE
1612 : : * ADD FOREIGN KEY to validate the initial table contents.
1613 : : *
1614 : : * We expect that the caller has made provision to prevent any problems
1615 : : * caused by concurrent actions. This could be either by locking rel and
1616 : : * pkrel at ShareRowExclusiveLock or higher, or by otherwise ensuring
1617 : : * that triggers implementing the checks are already active.
1618 : : * Hence, we do not need to lock individual rows for the check.
1619 : : *
1620 : : * If the check fails because the current user doesn't have permissions
1621 : : * to read both tables, return false to let our caller know that they will
1622 : : * need to do something else to check the constraint.
1623 : : */
1624 : : bool
7204 tgl@sss.pgh.pa.us 1625 : 776 : RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
1626 : : {
1627 : : const RI_ConstraintInfo *riinfo;
1628 : : StringInfoData querybuf;
1629 : : char pkrelname[MAX_QUOTED_REL_NAME_LEN];
1630 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
1631 : : char pkattname[MAX_QUOTED_NAME_LEN + 3];
1632 : : char fkattname[MAX_QUOTED_NAME_LEN + 3];
1633 : : RangeTblEntry *rte;
1634 : : RTEPermissionInfo *pk_perminfo;
1635 : : RTEPermissionInfo *fk_perminfo;
1281 alvherre@alvh.no-ip. 1636 : 776 : List *rtes = NIL;
1637 : 776 : List *perminfos = NIL;
1638 : : const char *sep;
1639 : : const char *fk_only;
1640 : : const char *pk_only;
1641 : : int save_nestlevel;
1642 : : char workmembuf[32];
1643 : : int spi_result;
1644 : : SPIPlanPtr qplan;
1645 : :
5251 tgl@sss.pgh.pa.us 1646 : 776 : riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
1647 : :
1648 : : /*
1649 : : * Check to make sure current user has enough permissions to do the test
1650 : : * query. (If not, caller can fall back to the trigger method, which
1651 : : * works because it changes user IDs on the fly.)
1652 : : *
1653 : : * XXX are there any other show-stopper conditions to check?
1654 : : */
1430 alvherre@alvh.no-ip. 1655 : 776 : pk_perminfo = makeNode(RTEPermissionInfo);
1656 : 776 : pk_perminfo->relid = RelationGetRelid(pk_rel);
1657 : 776 : pk_perminfo->requiredPerms = ACL_SELECT;
1281 1658 : 776 : perminfos = lappend(perminfos, pk_perminfo);
1659 : 776 : rte = makeNode(RangeTblEntry);
1660 : 776 : rte->rtekind = RTE_RELATION;
1661 : 776 : rte->relid = RelationGetRelid(pk_rel);
1662 : 776 : rte->relkind = pk_rel->rd_rel->relkind;
1663 : 776 : rte->rellockmode = AccessShareLock;
1664 : 776 : rte->perminfoindex = list_length(perminfos);
1665 : 776 : rtes = lappend(rtes, rte);
1666 : :
1430 1667 : 776 : fk_perminfo = makeNode(RTEPermissionInfo);
1668 : 776 : fk_perminfo->relid = RelationGetRelid(fk_rel);
1669 : 776 : fk_perminfo->requiredPerms = ACL_SELECT;
1281 1670 : 776 : perminfos = lappend(perminfos, fk_perminfo);
1671 : 776 : rte = makeNode(RangeTblEntry);
1672 : 776 : rte->rtekind = RTE_RELATION;
1673 : 776 : rte->relid = RelationGetRelid(fk_rel);
1674 : 776 : rte->relkind = fk_rel->rd_rel->relkind;
1675 : 776 : rte->rellockmode = AccessShareLock;
1676 : 776 : rte->perminfoindex = list_length(perminfos);
1677 : 776 : rtes = lappend(rtes, rte);
1678 : :
2807 peter@eisentraut.org 1679 [ + + ]: 1837 : for (int i = 0; i < riinfo->nkeys; i++)
1680 : : {
1681 : : int attno;
1682 : :
5251 tgl@sss.pgh.pa.us 1683 : 1061 : attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
1430 alvherre@alvh.no-ip. 1684 : 1061 : pk_perminfo->selectedCols = bms_add_member(pk_perminfo->selectedCols, attno);
1685 : :
5251 tgl@sss.pgh.pa.us 1686 : 1061 : attno = riinfo->fk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
1430 alvherre@alvh.no-ip. 1687 : 1061 : fk_perminfo->selectedCols = bms_add_member(fk_perminfo->selectedCols, attno);
1688 : : }
1689 : :
1281 1690 [ + + ]: 776 : if (!ExecCheckPermissions(rtes, perminfos, false))
5950 rhaas@postgresql.org 1691 : 41 : return false;
1692 : :
1693 : : /*
1694 : : * Also punt if RLS is enabled on either table unless this role has the
1695 : : * bypassrls right or is the table owner of the table(s) involved which
1696 : : * have RLS enabled.
1697 : : */
4335 alvherre@alvh.no-ip. 1698 [ + + ]: 735 : if (!has_bypassrls_privilege(GetUserId()) &&
4425 sfrost@snowman.net 1699 [ + - ]: 8 : ((pk_rel->rd_rel->relrowsecurity &&
1281 alvherre@alvh.no-ip. 1700 [ - + ]: 8 : !object_ownercheck(RelationRelationId, RelationGetRelid(pk_rel),
1281 alvherre@alvh.no-ip. 1701 :UBC 0 : GetUserId())) ||
4425 sfrost@snowman.net 1702 [ # # ]: 0 : (fk_rel->rd_rel->relrowsecurity &&
1281 alvherre@alvh.no-ip. 1703 [ # # ]: 0 : !object_ownercheck(RelationRelationId, RelationGetRelid(fk_rel),
1704 : : GetUserId()))))
4430 sfrost@snowman.net 1705 :CBC 8 : return false;
1706 : :
1707 : : /*----------
1708 : : * The query string built is:
1709 : : * SELECT fk.keycols FROM [ONLY] relname fk
1710 : : * LEFT OUTER JOIN [ONLY] pkrelname pk
1711 : : * ON (pk.pkkeycol1=fk.keycol1 [AND ...])
1712 : : * WHERE pk.pkkeycol1 IS NULL AND
1713 : : * For MATCH SIMPLE:
1714 : : * (fk.keycol1 IS NOT NULL [AND ...])
1715 : : * For MATCH FULL:
1716 : : * (fk.keycol1 IS NOT NULL [OR ...])
1717 : : *
1718 : : * We attach COLLATE clauses to the operators when comparing columns
1719 : : * that have different collations.
1720 : : *----------
1721 : : */
7204 tgl@sss.pgh.pa.us 1722 : 727 : initStringInfo(&querybuf);
4753 rhaas@postgresql.org 1723 : 727 : appendStringInfoString(&querybuf, "SELECT ");
8103 bruce@momjian.us 1724 : 727 : sep = "";
2807 peter@eisentraut.org 1725 [ + + ]: 1715 : for (int i = 0; i < riinfo->nkeys; i++)
1726 : : {
7204 tgl@sss.pgh.pa.us 1727 : 988 : quoteOneName(fkattname,
5251 1728 : 988 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
7204 1729 : 988 : appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
8431 1730 : 988 : sep = ", ";
1731 : : }
1732 : :
7204 1733 : 727 : quoteRelationName(pkrelname, pk_rel);
1734 : 727 : quoteRelationName(fkrelname, fk_rel);
3137 alvherre@alvh.no-ip. 1735 : 1454 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1736 [ - + ]: 727 : "" : "ONLY ";
2773 1737 : 1454 : pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1738 [ + + ]: 727 : "" : "ONLY ";
7204 tgl@sss.pgh.pa.us 1739 : 727 : appendStringInfo(&querybuf,
1740 : : " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
1741 : : fk_only, fkrelname, pk_only, pkrelname);
1742 : :
1743 : 727 : strcpy(pkattname, "pk.");
1744 : 727 : strcpy(fkattname, "fk.");
1745 : 727 : sep = "(";
2807 peter@eisentraut.org 1746 [ + + ]: 1715 : for (int i = 0; i < riinfo->nkeys; i++)
1747 : : {
5251 tgl@sss.pgh.pa.us 1748 : 988 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
1749 : 988 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
1750 : 988 : Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
1751 : 988 : Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
1752 : :
7204 1753 : 988 : quoteOneName(pkattname + 3,
5251 1754 : 988 : RIAttName(pk_rel, riinfo->pk_attnums[i]));
7204 1755 : 988 : quoteOneName(fkattname + 3,
5251 1756 : 988 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
7204 1757 : 988 : ri_GenerateQual(&querybuf, sep,
1758 : : pkattname, pk_type,
5251 1759 : 988 : riinfo->pf_eq_oprs[i],
1760 : : fkattname, fk_type);
5687 1761 [ + + ]: 988 : if (pk_coll != fk_coll)
1762 : 8 : ri_GenerateQualCollation(&querybuf, pk_coll);
7204 1763 : 988 : sep = "AND";
1764 : : }
1765 : :
1766 : : /*
1767 : : * It's sufficient to test any one pk attribute for null to detect a join
1768 : : * failure.
1769 : : */
5251 1770 : 727 : quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
7204 1771 : 727 : appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
1772 : :
8103 bruce@momjian.us 1773 : 727 : sep = "";
2807 peter@eisentraut.org 1774 [ + + ]: 1715 : for (int i = 0; i < riinfo->nkeys; i++)
1775 : : {
5251 tgl@sss.pgh.pa.us 1776 : 988 : quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
7204 1777 : 988 : appendStringInfo(&querybuf,
1778 : : "%sfk.%s IS NOT NULL",
1779 : : sep, fkattname);
5251 1780 [ + + - ]: 988 : switch (riinfo->confmatchtype)
1781 : : {
5254 1782 : 914 : case FKCONSTR_MATCH_SIMPLE:
8103 bruce@momjian.us 1783 : 914 : sep = " AND ";
8431 tgl@sss.pgh.pa.us 1784 : 914 : break;
1785 : 74 : case FKCONSTR_MATCH_FULL:
8103 bruce@momjian.us 1786 : 74 : sep = " OR ";
8431 tgl@sss.pgh.pa.us 1787 : 74 : break;
1788 : : }
1789 : : }
4753 rhaas@postgresql.org 1790 : 727 : appendStringInfoChar(&querybuf, ')');
1791 : :
1792 : : /*
1793 : : * Temporarily increase work_mem so that the check query can be executed
1794 : : * more efficiently. It seems okay to do this because the query is simple
1795 : : * enough to not use a multiple of work_mem, and one typically would not
1796 : : * have many large foreign-key validations happening concurrently. So
1797 : : * this seems to meet the criteria for being considered a "maintenance"
1798 : : * operation, and accordingly we use maintenance_work_mem. However, we
1799 : : * must also set hash_mem_multiplier to 1, since it is surely not okay to
1800 : : * let that get applied to the maintenance_work_mem value.
1801 : : *
1802 : : * We use the equivalent of a function SET option to allow the setting to
1803 : : * persist for exactly the duration of the check query. guc.c also takes
1804 : : * care of undoing the setting on error.
1805 : : */
5510 tgl@sss.pgh.pa.us 1806 : 727 : save_nestlevel = NewGUCNestLevel();
1807 : :
8311 1808 : 727 : snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem);
1809 : 727 : (void) set_config_option("work_mem", workmembuf,
1810 : : PGC_USERSET, PGC_S_SESSION,
1811 : : GUC_ACTION_SAVE, true, 0, false);
2290 pg@bowt.ie 1812 : 727 : (void) set_config_option("hash_mem_multiplier", "1",
1813 : : PGC_USERSET, PGC_S_SESSION,
1814 : : GUC_ACTION_SAVE, true, 0, false);
1815 : :
787 tgl@sss.pgh.pa.us 1816 : 727 : SPI_connect();
1817 : :
1818 : : /*
1819 : : * Generate the plan. We don't need to cache it, and there are no
1820 : : * arguments to the plan.
1821 : : */
7204 1822 : 727 : qplan = SPI_prepare(querybuf.data, 0, NULL);
1823 : :
8431 1824 [ - + ]: 727 : if (qplan == NULL)
3354 peter_e@gmx.net 1825 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare returned %s for %s",
1826 : : SPI_result_code_string(SPI_result), querybuf.data);
1827 : :
1828 : : /*
1829 : : * Run the plan. For safety we force a current snapshot to be used. (In
1830 : : * transaction-snapshot mode, this arguably violates transaction isolation
1831 : : * rules, but we really haven't got much choice.) We don't need to
1832 : : * register the snapshot, because SPI_execute_snapshot will see to it. We
1833 : : * need at most one tuple returned, so pass limit = 1.
1834 : : */
8088 tgl@sss.pgh.pa.us 1835 :CBC 727 : spi_result = SPI_execute_snapshot(qplan,
1836 : : NULL, NULL,
1837 : : GetLatestSnapshot(),
1838 : : InvalidSnapshot,
1839 : : true, false, 1);
1840 : :
1841 : : /* Check result */
8431 1842 [ - + ]: 727 : if (spi_result != SPI_OK_SELECT)
3354 peter_e@gmx.net 1843 [ # # ]:UBC 0 : elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result));
1844 : :
1845 : : /* Did we find a tuple violating the constraint? */
8431 tgl@sss.pgh.pa.us 1846 [ + + ]:CBC 727 : if (SPI_processed > 0)
1847 : : {
1848 : : TupleTableSlot *slot;
1849 : 47 : HeapTuple tuple = SPI_tuptable->vals[0];
1850 : 47 : TupleDesc tupdesc = SPI_tuptable->tupdesc;
1851 : : RI_ConstraintInfo fake_riinfo;
1852 : :
2809 andres@anarazel.de 1853 : 47 : slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
1854 : :
1855 : 47 : heap_deform_tuple(tuple, tupdesc,
1856 : : slot->tts_values, slot->tts_isnull);
1857 : 47 : ExecStoreVirtualTuple(slot);
1858 : :
1859 : : /*
1860 : : * The columns to look at in the result tuple are 1..N, not whatever
1861 : : * they are in the fk_rel. Hack up riinfo so that the subroutines
1862 : : * called here will behave properly.
1863 : : *
1864 : : * In addition to this, we have to pass the correct tupdesc to
1865 : : * ri_ReportViolation, overriding its normal habit of using the pk_rel
1866 : : * or fk_rel's tupdesc.
1867 : : */
5251 tgl@sss.pgh.pa.us 1868 : 47 : memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo));
2807 peter@eisentraut.org 1869 [ + + ]: 110 : for (int i = 0; i < fake_riinfo.nkeys; i++)
5251 tgl@sss.pgh.pa.us 1870 : 63 : fake_riinfo.fk_attnums[i] = i + 1;
1871 : :
1872 : : /*
1873 : : * If it's MATCH FULL, and there are any nulls in the FK keys,
1874 : : * complain about that rather than the lack of a match. MATCH FULL
1875 : : * disallows partially-null FK rows.
1876 : : */
1877 [ + + + + ]: 67 : if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
2809 andres@anarazel.de 1878 : 20 : ri_NullCheck(tupdesc, slot, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
5253 tgl@sss.pgh.pa.us 1879 [ + - ]: 8 : ereport(ERROR,
1880 : : (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
1881 : : errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
1882 : : RelationGetRelationName(fk_rel),
1883 : : NameStr(fake_riinfo.conname)),
1884 : : errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
1885 : : errtableconstraint(fk_rel,
1886 : : NameStr(fake_riinfo.conname))));
1887 : :
1888 : : /*
1889 : : * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
1890 : : * query, which isn't true, but will cause it to use
1891 : : * fake_riinfo.fk_attnums as we need.
1892 : : */
5251 1893 : 39 : ri_ReportViolation(&fake_riinfo,
1894 : : pk_rel, fk_rel,
1895 : : slot, tupdesc,
1896 : : RI_PLAN_CHECK_LOOKUPPK, false, false);
1897 : :
1898 : : ExecDropSingleTupleTableSlot(slot);
1899 : : }
1900 : :
8431 1901 [ - + ]: 680 : if (SPI_finish() != SPI_OK_FINISH)
8431 tgl@sss.pgh.pa.us 1902 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
1903 : :
1904 : : /*
1905 : : * Restore work_mem and hash_mem_multiplier.
1906 : : */
5510 tgl@sss.pgh.pa.us 1907 :CBC 680 : AtEOXact_GUC(true, save_nestlevel);
1908 : :
8431 1909 : 680 : return true;
1910 : : }
1911 : :
1912 : : /*
1913 : : * RI_PartitionRemove_Check -
1914 : : *
1915 : : * Verify no referencing values exist, when a partition is detached on
1916 : : * the referenced side of a foreign key constraint.
1917 : : */
1918 : : void
2773 alvherre@alvh.no-ip. 1919 : 65 : RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
1920 : : {
1921 : : const RI_ConstraintInfo *riinfo;
1922 : : StringInfoData querybuf;
1923 : : char *constraintDef;
1924 : : char pkrelname[MAX_QUOTED_REL_NAME_LEN];
1925 : : char fkrelname[MAX_QUOTED_REL_NAME_LEN];
1926 : : char pkattname[MAX_QUOTED_NAME_LEN + 3];
1927 : : char fkattname[MAX_QUOTED_NAME_LEN + 3];
1928 : : const char *sep;
1929 : : const char *fk_only;
1930 : : int save_nestlevel;
1931 : : char workmembuf[32];
1932 : : int spi_result;
1933 : : SPIPlanPtr qplan;
1934 : : int i;
1935 : :
1936 : 65 : riinfo = ri_FetchConstraintInfo(trigger, fk_rel, false);
1937 : :
1938 : : /*
1939 : : * We don't check permissions before displaying the error message, on the
1940 : : * assumption that the user detaching the partition must have enough
1941 : : * privileges to examine the table contents anyhow.
1942 : : */
1943 : :
1944 : : /*----------
1945 : : * The query string built is:
1946 : : * SELECT fk.keycols FROM [ONLY] relname fk
1947 : : * JOIN pkrelname pk
1948 : : * ON (pk.pkkeycol1=fk.keycol1 [AND ...])
1949 : : * WHERE (<partition constraint>) AND
1950 : : * For MATCH SIMPLE:
1951 : : * (fk.keycol1 IS NOT NULL [AND ...])
1952 : : * For MATCH FULL:
1953 : : * (fk.keycol1 IS NOT NULL [OR ...])
1954 : : *
1955 : : * We attach COLLATE clauses to the operators when comparing columns
1956 : : * that have different collations.
1957 : : *----------
1958 : : */
1959 : 65 : initStringInfo(&querybuf);
1960 : 65 : appendStringInfoString(&querybuf, "SELECT ");
1961 : 65 : sep = "";
1962 [ + + ]: 130 : for (i = 0; i < riinfo->nkeys; i++)
1963 : : {
1964 : 65 : quoteOneName(fkattname,
1965 : 65 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
1966 : 65 : appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
1967 : 65 : sep = ", ";
1968 : : }
1969 : :
1970 : 65 : quoteRelationName(pkrelname, pk_rel);
1971 : 65 : quoteRelationName(fkrelname, fk_rel);
1972 : 130 : fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
1973 [ + + ]: 65 : "" : "ONLY ";
1974 : 65 : appendStringInfo(&querybuf,
1975 : : " FROM %s%s fk JOIN %s pk ON",
1976 : : fk_only, fkrelname, pkrelname);
1977 : 65 : strcpy(pkattname, "pk.");
1978 : 65 : strcpy(fkattname, "fk.");
1979 : 65 : sep = "(";
1980 [ + + ]: 130 : for (i = 0; i < riinfo->nkeys; i++)
1981 : : {
1982 : 65 : Oid pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
1983 : 65 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
1984 : 65 : Oid pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
1985 : 65 : Oid fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
1986 : :
1987 : 65 : quoteOneName(pkattname + 3,
1988 : 65 : RIAttName(pk_rel, riinfo->pk_attnums[i]));
1989 : 65 : quoteOneName(fkattname + 3,
1990 : 65 : RIAttName(fk_rel, riinfo->fk_attnums[i]));
1991 : 65 : ri_GenerateQual(&querybuf, sep,
1992 : : pkattname, pk_type,
1993 : 65 : riinfo->pf_eq_oprs[i],
1994 : : fkattname, fk_type);
1995 [ - + ]: 65 : if (pk_coll != fk_coll)
2773 alvherre@alvh.no-ip. 1996 :UBC 0 : ri_GenerateQualCollation(&querybuf, pk_coll);
2773 alvherre@alvh.no-ip. 1997 :CBC 65 : sep = "AND";
1998 : : }
1999 : :
2000 : : /*
2001 : : * Start the WHERE clause with the partition constraint (except if this is
2002 : : * the default partition and there's no other partition, because the
2003 : : * partition constraint is the empty string in that case.)
2004 : : */
2005 : 65 : constraintDef = pg_get_partconstrdef_string(RelationGetRelid(pk_rel), "pk");
2006 [ + - + - ]: 65 : if (constraintDef && constraintDef[0] != '\0')
2007 : 65 : appendStringInfo(&querybuf, ") WHERE %s AND (",
2008 : : constraintDef);
2009 : : else
2212 drowley@postgresql.o 2010 :UBC 0 : appendStringInfoString(&querybuf, ") WHERE (");
2011 : :
2773 alvherre@alvh.no-ip. 2012 :CBC 65 : sep = "";
2013 [ + + ]: 130 : for (i = 0; i < riinfo->nkeys; i++)
2014 : : {
2015 : 65 : quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
2016 : 65 : appendStringInfo(&querybuf,
2017 : : "%sfk.%s IS NOT NULL",
2018 : : sep, fkattname);
2019 [ + - - ]: 65 : switch (riinfo->confmatchtype)
2020 : : {
2021 : 65 : case FKCONSTR_MATCH_SIMPLE:
2022 : 65 : sep = " AND ";
2023 : 65 : break;
2773 alvherre@alvh.no-ip. 2024 :UBC 0 : case FKCONSTR_MATCH_FULL:
2025 : 0 : sep = " OR ";
2026 : 0 : break;
2027 : : }
2028 : : }
2773 alvherre@alvh.no-ip. 2029 :CBC 65 : appendStringInfoChar(&querybuf, ')');
2030 : :
2031 : : /*
2032 : : * Temporarily increase work_mem so that the check query can be executed
2033 : : * more efficiently. It seems okay to do this because the query is simple
2034 : : * enough to not use a multiple of work_mem, and one typically would not
2035 : : * have many large foreign-key validations happening concurrently. So
2036 : : * this seems to meet the criteria for being considered a "maintenance"
2037 : : * operation, and accordingly we use maintenance_work_mem. However, we
2038 : : * must also set hash_mem_multiplier to 1, since it is surely not okay to
2039 : : * let that get applied to the maintenance_work_mem value.
2040 : : *
2041 : : * We use the equivalent of a function SET option to allow the setting to
2042 : : * persist for exactly the duration of the check query. guc.c also takes
2043 : : * care of undoing the setting on error.
2044 : : */
2045 : 65 : save_nestlevel = NewGUCNestLevel();
2046 : :
2047 : 65 : snprintf(workmembuf, sizeof(workmembuf), "%d", maintenance_work_mem);
2048 : 65 : (void) set_config_option("work_mem", workmembuf,
2049 : : PGC_USERSET, PGC_S_SESSION,
2050 : : GUC_ACTION_SAVE, true, 0, false);
2290 pg@bowt.ie 2051 : 65 : (void) set_config_option("hash_mem_multiplier", "1",
2052 : : PGC_USERSET, PGC_S_SESSION,
2053 : : GUC_ACTION_SAVE, true, 0, false);
2054 : :
787 tgl@sss.pgh.pa.us 2055 : 65 : SPI_connect();
2056 : :
2057 : : /*
2058 : : * Generate the plan. We don't need to cache it, and there are no
2059 : : * arguments to the plan.
2060 : : */
2773 alvherre@alvh.no-ip. 2061 : 65 : qplan = SPI_prepare(querybuf.data, 0, NULL);
2062 : :
2063 [ - + ]: 65 : if (qplan == NULL)
2773 alvherre@alvh.no-ip. 2064 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare returned %s for %s",
2065 : : SPI_result_code_string(SPI_result), querybuf.data);
2066 : :
2067 : : /*
2068 : : * Run the plan. For safety we force a current snapshot to be used. (In
2069 : : * transaction-snapshot mode, this arguably violates transaction isolation
2070 : : * rules, but we really haven't got much choice.) We don't need to
2071 : : * register the snapshot, because SPI_execute_snapshot will see to it. We
2072 : : * need at most one tuple returned, so pass limit = 1.
2073 : : */
2773 alvherre@alvh.no-ip. 2074 :CBC 65 : spi_result = SPI_execute_snapshot(qplan,
2075 : : NULL, NULL,
2076 : : GetLatestSnapshot(),
2077 : : InvalidSnapshot,
2078 : : true, false, 1);
2079 : :
2080 : : /* Check result */
2081 [ - + ]: 65 : if (spi_result != SPI_OK_SELECT)
2773 alvherre@alvh.no-ip. 2082 [ # # ]:UBC 0 : elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result));
2083 : :
2084 : : /* Did we find a tuple that would violate the constraint? */
2773 alvherre@alvh.no-ip. 2085 [ + + ]:CBC 65 : if (SPI_processed > 0)
2086 : : {
2087 : : TupleTableSlot *slot;
2088 : 22 : HeapTuple tuple = SPI_tuptable->vals[0];
2089 : 22 : TupleDesc tupdesc = SPI_tuptable->tupdesc;
2090 : : RI_ConstraintInfo fake_riinfo;
2091 : :
2092 : 22 : slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual);
2093 : :
2094 : 22 : heap_deform_tuple(tuple, tupdesc,
2095 : : slot->tts_values, slot->tts_isnull);
2096 : 22 : ExecStoreVirtualTuple(slot);
2097 : :
2098 : : /*
2099 : : * The columns to look at in the result tuple are 1..N, not whatever
2100 : : * they are in the fk_rel. Hack up riinfo so that ri_ReportViolation
2101 : : * will behave properly.
2102 : : *
2103 : : * In addition to this, we have to pass the correct tupdesc to
2104 : : * ri_ReportViolation, overriding its normal habit of using the pk_rel
2105 : : * or fk_rel's tupdesc.
2106 : : */
2107 : 22 : memcpy(&fake_riinfo, riinfo, sizeof(RI_ConstraintInfo));
2108 [ + + ]: 44 : for (i = 0; i < fake_riinfo.nkeys; i++)
2109 : 22 : fake_riinfo.pk_attnums[i] = i + 1;
2110 : :
2111 : 22 : ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel,
2112 : : slot, tupdesc, 0, false, true);
2113 : : }
2114 : :
2115 [ - + ]: 43 : if (SPI_finish() != SPI_OK_FINISH)
2773 alvherre@alvh.no-ip. 2116 [ # # ]:UBC 0 : elog(ERROR, "SPI_finish failed");
2117 : :
2118 : : /*
2119 : : * Restore work_mem and hash_mem_multiplier.
2120 : : */
2773 alvherre@alvh.no-ip. 2121 :CBC 43 : AtEOXact_GUC(true, save_nestlevel);
2122 : 43 : }
2123 : :
2124 : :
2125 : : /* ----------
2126 : : * Local functions below
2127 : : * ----------
2128 : : */
2129 : :
2130 : :
2131 : : /*
2132 : : * quoteOneName --- safely quote a single SQL name
2133 : : *
2134 : : * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
2135 : : */
2136 : : static void
8984 tgl@sss.pgh.pa.us 2137 : 13379 : quoteOneName(char *buffer, const char *name)
2138 : : {
2139 : : /* Rather than trying to be smart, just always quote it. */
2140 : 13379 : *buffer++ = '"';
2141 [ + + ]: 82942 : while (*name)
2142 : : {
2143 [ - + ]: 69563 : if (*name == '"')
8984 tgl@sss.pgh.pa.us 2144 :UBC 0 : *buffer++ = '"';
8984 tgl@sss.pgh.pa.us 2145 :CBC 69563 : *buffer++ = *name++;
2146 : : }
2147 : 13379 : *buffer++ = '"';
2148 : 13379 : *buffer = '\0';
2149 : 13379 : }
2150 : :
2151 : : /*
2152 : : * quoteRelationName --- safely quote a fully qualified relation name
2153 : : *
2154 : : * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
2155 : : */
2156 : : static void
2157 : 3030 : quoteRelationName(char *buffer, Relation rel)
2158 : : {
8983 2159 : 3030 : quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
8984 2160 : 3030 : buffer += strlen(buffer);
2161 : 3030 : *buffer++ = '.';
2162 : 3030 : quoteOneName(buffer, RelationGetRelationName(rel));
2163 : 3030 : }
2164 : :
2165 : : /*
2166 : : * ri_GenerateQual --- generate a WHERE clause equating two variables
2167 : : *
2168 : : * This basically appends " sep leftop op rightop" to buf, adding casts
2169 : : * and schema qualification as needed to ensure that the parser will select
2170 : : * the operator we specify. leftop and rightop should be parenthesized
2171 : : * if they aren't variables or parameters.
2172 : : */
2173 : : static void
7204 2174 : 3254 : ri_GenerateQual(StringInfo buf,
2175 : : const char *sep,
2176 : : const char *leftop, Oid leftoptype,
2177 : : Oid opoid,
2178 : : const char *rightop, Oid rightoptype)
2179 : : {
3153 2180 : 3254 : appendStringInfo(buf, " %s ", sep);
2181 : 3254 : generate_operator_clause(buf, leftop, leftoptype, opoid,
2182 : : rightop, rightoptype);
6846 2183 : 3254 : }
2184 : :
2185 : : /*
2186 : : * ri_GenerateQualCollation --- add a COLLATE spec to a WHERE clause
2187 : : *
2188 : : * We only have to use this function when directly comparing the referencing
2189 : : * and referenced columns, if they are of different collations; else the
2190 : : * parser will fail to resolve the collation to use. We don't need to use
2191 : : * this function for RI queries that compare a variable to a $n parameter.
2192 : : * Since parameter symbols always have default collation, the effect will be
2193 : : * to use the variable's collation.
2194 : : *
2195 : : * Note that we require that the collations of the referencing and the
2196 : : * referenced column have the same notion of equality: Either they have to
2197 : : * both be deterministic or else they both have to be the same. (See also
2198 : : * ATAddForeignKeyConstraint().)
2199 : : */
2200 : : static void
5687 2201 : 8 : ri_GenerateQualCollation(StringInfo buf, Oid collation)
2202 : : {
2203 : : HeapTuple tp;
2204 : : Form_pg_collation colltup;
2205 : : char *collname;
2206 : : char onename[MAX_QUOTED_NAME_LEN];
2207 : :
2208 : : /* Nothing to do if it's a noncollatable data type */
2209 [ - + ]: 8 : if (!OidIsValid(collation))
5687 tgl@sss.pgh.pa.us 2210 :UBC 0 : return;
2211 : :
5687 tgl@sss.pgh.pa.us 2212 :CBC 8 : tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collation));
2213 [ - + ]: 8 : if (!HeapTupleIsValid(tp))
5687 tgl@sss.pgh.pa.us 2214 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for collation %u", collation);
5687 tgl@sss.pgh.pa.us 2215 :CBC 8 : colltup = (Form_pg_collation) GETSTRUCT(tp);
2216 : 8 : collname = NameStr(colltup->collname);
2217 : :
2218 : : /*
2219 : : * We qualify the name always, for simplicity and to ensure the query is
2220 : : * not search-path-dependent.
2221 : : */
2222 : 8 : quoteOneName(onename, get_namespace_name(colltup->collnamespace));
2223 : 8 : appendStringInfo(buf, " COLLATE %s", onename);
2224 : 8 : quoteOneName(onename, collname);
2225 : 8 : appendStringInfo(buf, ".%s", onename);
2226 : :
2227 : 8 : ReleaseSysCache(tp);
2228 : : }
2229 : :
2230 : : /* ----------
2231 : : * ri_BuildQueryKey -
2232 : : *
2233 : : * Construct a hashtable key for a prepared SPI plan of an FK constraint.
2234 : : *
2235 : : * key: output argument, *key is filled in based on the other arguments
2236 : : * riinfo: info derived from pg_constraint entry
2237 : : * constr_queryno: an internal number identifying the query type
2238 : : * (see RI_PLAN_XXX constants at head of file)
2239 : : * ----------
2240 : : */
2241 : : static void
5253 2242 : 2769 : ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
2243 : : int32 constr_queryno)
2244 : : {
2245 : : /*
2246 : : * Inherited constraints with a common ancestor can share ri_query_cache
2247 : : * entries for all query types except RI_PLAN_CHECK_LOOKUPPK_FROM_PK.
2248 : : * Except in that case, the query processes the other table involved in
2249 : : * the FK constraint (i.e., not the table on which the trigger has been
2250 : : * fired), and so it will be the same for all members of the inheritance
2251 : : * tree. So we may use the root constraint's OID in the hash key, rather
2252 : : * than the constraint's own OID. This avoids creating duplicate SPI
2253 : : * plans, saving lots of work and memory when there are many partitions
2254 : : * with similar FK constraints.
2255 : : *
2256 : : * (Note that we must still have a separate RI_ConstraintInfo for each
2257 : : * constraint, because partitions can have different column orders,
2258 : : * resulting in different pk_attnums[] or fk_attnums[] array contents.)
2259 : : *
2260 : : * We assume struct RI_QueryKey contains no padding bytes, else we'd need
2261 : : * to use memset to clear them.
2262 : : */
1673 alvherre@alvh.no-ip. 2263 [ + + ]: 2769 : if (constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK)
2264 : 2239 : key->constr_id = riinfo->constraint_root_id;
2265 : : else
2266 : 530 : key->constr_id = riinfo->constraint_id;
9703 bruce@momjian.us 2267 : 2769 : key->constr_queryno = constr_queryno;
9890 JanWieck@Yahoo.com 2268 : 2769 : }
2269 : :
2270 : : /*
2271 : : * Check that RI trigger function was called in expected context
2272 : : */
2273 : : static void
8636 tgl@sss.pgh.pa.us 2274 : 607129 : ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname, int tgkind)
2275 : : {
2276 : 607129 : TriggerData *trigdata = (TriggerData *) fcinfo->context;
2277 : :
2278 [ + - - + ]: 607129 : if (!CALLED_AS_TRIGGER(fcinfo))
8507 tgl@sss.pgh.pa.us 2279 [ # # ]:UBC 0 : ereport(ERROR,
2280 : : (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
2281 : : errmsg("function \"%s\" was not called by trigger manager", funcname)));
2282 : :
2283 : : /*
2284 : : * Check proper event
2285 : : */
8636 tgl@sss.pgh.pa.us 2286 [ + - ]:CBC 607129 : if (!TRIGGER_FIRED_AFTER(trigdata->tg_event) ||
2287 [ - + ]: 607129 : !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
8507 tgl@sss.pgh.pa.us 2288 [ # # ]:UBC 0 : ereport(ERROR,
2289 : : (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
2290 : : errmsg("function \"%s\" must be fired AFTER ROW", funcname)));
2291 : :
8636 tgl@sss.pgh.pa.us 2292 [ + + + - ]:CBC 607129 : switch (tgkind)
2293 : : {
2294 : 605618 : case RI_TRIGTYPE_INSERT:
2295 [ - + ]: 605618 : if (!TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
8507 tgl@sss.pgh.pa.us 2296 [ # # ]:UBC 0 : ereport(ERROR,
2297 : : (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
2298 : : errmsg("function \"%s\" must be fired for INSERT", funcname)));
8636 tgl@sss.pgh.pa.us 2299 :CBC 605618 : break;
2300 : 863 : case RI_TRIGTYPE_UPDATE:
2301 [ - + ]: 863 : if (!TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
8507 tgl@sss.pgh.pa.us 2302 [ # # ]:UBC 0 : ereport(ERROR,
2303 : : (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
2304 : : errmsg("function \"%s\" must be fired for UPDATE", funcname)));
8636 tgl@sss.pgh.pa.us 2305 :CBC 863 : break;
2306 : 648 : case RI_TRIGTYPE_DELETE:
2307 [ - + ]: 648 : if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
8507 tgl@sss.pgh.pa.us 2308 [ # # ]:UBC 0 : ereport(ERROR,
2309 : : (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
2310 : : errmsg("function \"%s\" must be fired for DELETE", funcname)));
8636 tgl@sss.pgh.pa.us 2311 :CBC 648 : break;
2312 : : }
7204 2313 : 607129 : }
2314 : :
2315 : :
2316 : : /*
2317 : : * Fetch the RI_ConstraintInfo struct for the trigger's FK constraint.
2318 : : */
2319 : : static RI_ConstraintInfo *
5251 2320 : 610368 : ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
2321 : : {
7204 2322 : 610368 : Oid constraintOid = trigger->tgconstraint;
2323 : : RI_ConstraintInfo *riinfo;
2324 : :
2325 : : /*
2326 : : * Check that the FK constraint's OID is available; it might not be if
2327 : : * we've been invoked via an ordinary trigger or an old-style "constraint
2328 : : * trigger".
2329 : : */
2330 [ - + ]: 610368 : if (!OidIsValid(constraintOid))
8507 tgl@sss.pgh.pa.us 2331 [ # # ]:UBC 0 : ereport(ERROR,
2332 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2333 : : errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"",
2334 : : trigger->tgname, RelationGetRelationName(trig_rel)),
2335 : : errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT.")));
2336 : :
2337 : : /* Find or create a hashtable entry for the constraint */
5251 tgl@sss.pgh.pa.us 2338 :CBC 610368 : riinfo = ri_LoadConstraintInfo(constraintOid);
2339 : :
2340 : : /* Do some easy cross-checks against the trigger call data */
7204 2341 [ + + ]: 610368 : if (rel_is_pk)
2342 : : {
5251 2343 [ + - ]: 2943 : if (riinfo->fk_relid != trigger->tgconstrrelid ||
2344 [ - + ]: 2943 : riinfo->pk_relid != RelationGetRelid(trig_rel))
7204 tgl@sss.pgh.pa.us 2345 [ # # ]:UBC 0 : elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
2346 : : trigger->tgname, RelationGetRelationName(trig_rel));
2347 : : }
2348 : : else
2349 : : {
2787 alvherre@alvh.no-ip. 2350 [ + - ]:CBC 607425 : if (riinfo->fk_relid != RelationGetRelid(trig_rel) ||
2351 [ - + ]: 607425 : riinfo->pk_relid != trigger->tgconstrrelid)
2787 alvherre@alvh.no-ip. 2352 [ # # ]:UBC 0 : elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
2353 : : trigger->tgname, RelationGetRelationName(trig_rel));
2354 : : }
2355 : :
2807 peter@eisentraut.org 2356 [ + + ]:CBC 610368 : if (riinfo->confmatchtype != FKCONSTR_MATCH_FULL &&
2357 [ + - ]: 610055 : riinfo->confmatchtype != FKCONSTR_MATCH_PARTIAL &&
2358 [ - + ]: 610055 : riinfo->confmatchtype != FKCONSTR_MATCH_SIMPLE)
2807 peter@eisentraut.org 2359 [ # # ]:UBC 0 : elog(ERROR, "unrecognized confmatchtype: %d",
2360 : : riinfo->confmatchtype);
2361 : :
2807 peter@eisentraut.org 2362 [ - + ]:CBC 610368 : if (riinfo->confmatchtype == FKCONSTR_MATCH_PARTIAL)
2807 peter@eisentraut.org 2363 [ # # ]:UBC 0 : ereport(ERROR,
2364 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2365 : : errmsg("MATCH PARTIAL not yet implemented")));
2366 : :
5251 tgl@sss.pgh.pa.us 2367 :CBC 610368 : return riinfo;
2368 : : }
2369 : :
2370 : : /*
2371 : : * Fetch or create the RI_ConstraintInfo struct for an FK constraint.
2372 : : */
2373 : : static RI_ConstraintInfo *
2374 : 1216085 : ri_LoadConstraintInfo(Oid constraintOid)
2375 : : {
2376 : : RI_ConstraintInfo *riinfo;
2377 : : bool found;
2378 : : HeapTuple tup;
2379 : : Form_pg_constraint conForm;
2380 : :
2381 : : /*
2382 : : * On the first call initialize the hashtable
2383 : : */
2384 [ + + ]: 1216085 : if (!ri_constraint_cache)
2385 : 258 : ri_InitHashTables();
2386 : :
2387 : : /*
2388 : : * Find or create a hash entry. If we find a valid one, just return it.
2389 : : */
2390 : 1216085 : riinfo = (RI_ConstraintInfo *) hash_search(ri_constraint_cache,
2391 : : &constraintOid,
2392 : : HASH_ENTER, &found);
2393 [ + + ]: 1216085 : if (!found)
2394 : 2520 : riinfo->valid = false;
2395 [ + + ]: 1213565 : else if (riinfo->valid)
2396 : 1213274 : return riinfo;
2397 : :
2398 : : /*
2399 : : * Fetch the pg_constraint row so we can fill in the entry.
2400 : : */
2401 : 2811 : tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid));
2402 [ - + ]: 2811 : if (!HeapTupleIsValid(tup)) /* should not happen */
5251 tgl@sss.pgh.pa.us 2403 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", constraintOid);
5251 tgl@sss.pgh.pa.us 2404 :CBC 2811 : conForm = (Form_pg_constraint) GETSTRUCT(tup);
2405 : :
4908 bruce@momjian.us 2406 [ - + ]: 2811 : if (conForm->contype != CONSTRAINT_FOREIGN) /* should not happen */
5251 tgl@sss.pgh.pa.us 2407 [ # # ]:UBC 0 : elog(ERROR, "constraint %u is not a foreign key constraint",
2408 : : constraintOid);
2409 : :
2410 : : /* And extract data */
5251 tgl@sss.pgh.pa.us 2411 [ - + ]:CBC 2811 : Assert(riinfo->constraint_id == constraintOid);
2066 2412 [ + + ]: 2811 : if (OidIsValid(conForm->conparentid))
2413 : 940 : riinfo->constraint_root_id =
2414 : 940 : get_ri_constraint_root(conForm->conparentid);
2415 : : else
2416 : 1871 : riinfo->constraint_root_id = constraintOid;
5251 2417 : 2811 : riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID,
2418 : : ObjectIdGetDatum(constraintOid));
2066 2419 : 2811 : riinfo->rootHashValue = GetSysCacheHashValue1(CONSTROID,
2420 : : ObjectIdGetDatum(riinfo->constraint_root_id));
7204 2421 : 2811 : memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData));
2422 : 2811 : riinfo->pk_relid = conForm->confrelid;
2423 : 2811 : riinfo->fk_relid = conForm->conrelid;
2424 : 2811 : riinfo->confupdtype = conForm->confupdtype;
2425 : 2811 : riinfo->confdeltype = conForm->confdeltype;
2426 : 2811 : riinfo->confmatchtype = conForm->confmatchtype;
779 peter@eisentraut.org 2427 : 2811 : riinfo->hasperiod = conForm->conperiod;
2428 : :
2848 alvherre@alvh.no-ip. 2429 : 2811 : DeconstructFkConstraintRow(tup,
2430 : : &riinfo->nkeys,
2431 : 2811 : riinfo->fk_attnums,
2432 : 2811 : riinfo->pk_attnums,
2433 : 2811 : riinfo->pf_eq_oprs,
2434 : 2811 : riinfo->pp_eq_oprs,
1793 peter@eisentraut.org 2435 : 2811 : riinfo->ff_eq_oprs,
2436 : : &riinfo->ndelsetcols,
2437 : 2811 : riinfo->confdelsetcols);
2438 : :
2439 : : /*
2440 : : * For temporal FKs, get the operators and functions we need. We ask the
2441 : : * opclass of the PK element for these. This all gets cached (as does the
2442 : : * generated plan), so there's no performance issue.
2443 : : */
779 2444 [ + + ]: 2811 : if (riinfo->hasperiod)
2445 : : {
2446 : 137 : Oid opclass = get_index_column_opclass(conForm->conindid, riinfo->nkeys);
2447 : :
2448 : 137 : FindFKPeriodOpers(opclass,
2449 : : &riinfo->period_contained_by_oper,
2450 : : &riinfo->agged_period_contained_by_oper,
2451 : : &riinfo->period_intersect_oper);
2452 : : }
2453 : :
2454 : : /* Metadata used by fast path. */
219 amitlan@postgresql.o 2455 : 2811 : riinfo->conindid = conForm->conindid;
2456 : 2811 : riinfo->pk_is_partitioned =
2457 : 2811 : (get_rel_relkind(riinfo->pk_relid) == RELKIND_PARTITIONED_TABLE);
55 2458 : 2811 : riinfo->fastpath_state = RI_FASTPATH_UNKNOWN;
2459 : :
7204 tgl@sss.pgh.pa.us 2460 : 2811 : ReleaseSysCache(tup);
2461 : :
2462 : : /*
2463 : : * For efficient processing of invalidation messages below, we keep a
2464 : : * doubly-linked count list of all currently valid entries.
2465 : : */
1464 drowley@postgresql.o 2466 : 2811 : dclist_push_tail(&ri_constraint_cache_valid_list, &riinfo->valid_link);
2467 : :
5251 tgl@sss.pgh.pa.us 2468 : 2811 : riinfo->valid = true;
2469 : :
219 amitlan@postgresql.o 2470 : 2811 : riinfo->fpmeta = NULL;
2471 : :
5251 tgl@sss.pgh.pa.us 2472 : 2811 : return riinfo;
2473 : : }
2474 : :
2475 : : /*
2476 : : * get_ri_constraint_root
2477 : : * Returns the OID of the constraint's root parent
2478 : : */
2479 : : static Oid
2066 2480 : 940 : get_ri_constraint_root(Oid constrOid)
2481 : : {
2482 : : for (;;)
2483 : 232 : {
2484 : : HeapTuple tuple;
2485 : : Oid constrParentOid;
2486 : :
2487 : 1172 : tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
2488 [ - + ]: 1172 : if (!HeapTupleIsValid(tuple))
2066 tgl@sss.pgh.pa.us 2489 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for constraint %u", constrOid);
2066 tgl@sss.pgh.pa.us 2490 :CBC 1172 : constrParentOid = ((Form_pg_constraint) GETSTRUCT(tuple))->conparentid;
2491 : 1172 : ReleaseSysCache(tuple);
2492 [ + + ]: 1172 : if (!OidIsValid(constrParentOid))
2493 : 940 : break; /* we reached the root constraint */
2494 : 232 : constrOid = constrParentOid;
2495 : : }
2496 : 940 : return constrOid;
2497 : : }
2498 : :
2499 : : /*
2500 : : * Callback for pg_constraint, pg_amop and pg_cast inval events
2501 : : *
2502 : : * While most syscache callbacks just flush all their entries, pg_constraint
2503 : : * gets enough update traffic that it's probably worth being smarter.
2504 : : * Invalidate any ri_constraint_cache entry associated with the syscache
2505 : : * entry with the specified hash value, or all entries if hashvalue == 0.
2506 : : *
2507 : : * Note: at the time a cache invalidation message is processed there may be
2508 : : * active references to the cache. Because of this we never remove entries
2509 : : * from the cache, but only mark them invalid, which is harmless to active
2510 : : * uses. (Any query using an entry should hold a lock sufficient to keep that
2511 : : * data from changing under it --- but we may get cache flushes anyway.)
2512 : : *
2513 : : * The fast-path metadata hanging off an entry is subject to the same rule.
2514 : : * We unlink it so that the next check rebuilds it, but the object itself is
2515 : : * only queued here and is actually released by AtEOXact_RI().
2516 : : */
2517 : : static void
260 michael@paquier.xyz 2518 : 55072 : InvalidateConstraintCacheCallBack(Datum arg, SysCacheIdentifier cacheid,
2519 : : uint32 hashvalue)
2520 : : {
2521 : : dlist_mutable_iter iter;
2522 : :
5251 tgl@sss.pgh.pa.us 2523 [ - + ]: 55072 : Assert(ri_constraint_cache != NULL);
2524 : :
2525 : : /*
2526 : : * pg_amop changes can affect any constraint's fast-path metadata, and
2527 : : * this pg_amop hashvalue can't be matched against the pg_constraint-keyed
2528 : : * cache entries, so flush them all via the match-everything path below as
2529 : : * the large-list reset below does. Being selective would mean mapping
2530 : : * the change back to the affected constraints, not worth it for DDL this
2531 : : * rare.
2532 : : */
47 amitlan@postgresql.o 2533 [ + + ]: 55072 : if (cacheid == AMOPOPID)
2534 : 507 : hashvalue = 0;
2535 : :
2536 : : /*
2537 : : * Likewise for pg_cast: whether a constraint's FK values are
2538 : : * binary-coercible to the operator's input type, which decides fast-path
2539 : : * eligibility, can depend on a binary cast, which may be replaced.
2540 : : */
21 2541 [ + + ]: 55072 : if (cacheid == CASTSOURCETARGET)
2542 : 165 : hashvalue = 0;
2543 : :
2544 : : /*
2545 : : * If the list of currently valid entries gets excessively large, we mark
2546 : : * them all invalid so we can empty the list. This arrangement avoids
2547 : : * O(N^2) behavior in situations where a session touches many foreign keys
2548 : : * and also does many ALTER TABLEs, such as a restore from pg_dump.
2549 : : */
1464 drowley@postgresql.o 2550 [ - + ]: 55072 : if (dclist_count(&ri_constraint_cache_valid_list) > 1000)
4059 tgl@sss.pgh.pa.us 2551 :UBC 0 : hashvalue = 0; /* pretend it's a cache reset */
2552 : :
1464 drowley@postgresql.o 2553 [ + - + + ]:CBC 213012 : dclist_foreach_modify(iter, &ri_constraint_cache_valid_list)
2554 : : {
2555 : 157940 : RI_ConstraintInfo *riinfo = dclist_container(RI_ConstraintInfo,
2556 : : valid_link, iter.cur);
2557 : :
2558 : : /*
2559 : : * We must invalidate not only entries directly matching the given
2560 : : * hash value, but also child entries, in case the invalidation
2561 : : * affects a root constraint.
2562 : : */
2066 tgl@sss.pgh.pa.us 2563 [ + + ]: 157940 : if (hashvalue == 0 ||
2564 [ + + ]: 157758 : riinfo->oidHashValue == hashvalue ||
2565 [ + + ]: 155986 : riinfo->rootHashValue == hashvalue)
2566 : : {
4059 2567 : 2170 : riinfo->valid = false;
2568 : :
2569 : : /*
2570 : : * Detach any fast-path metadata so that the next check
2571 : : * repopulates it, but do not free it here. ri_FastPathCheck()
2572 : : * copies riinfo->fpmeta into a local and then does catalog
2573 : : * lookups, index scans, tuple locking, and calls equality
2574 : : * functions, all of which can accept invalidation messages and
2575 : : * reach this callback. Freeing now would leave it reading freed
2576 : : * memory. Queue it instead; AtEOXact_RI() releases it once no RI
2577 : : * check can be running.
2578 : : */
218 amitlan@postgresql.o 2579 [ + + ]: 2170 : if (riinfo->fpmeta)
2580 : : {
78 2581 : 700 : riinfo->fpmeta->next_dead = ri_fpmeta_dead_list;
2582 : 700 : ri_fpmeta_dead_list = riinfo->fpmeta;
218 2583 : 700 : riinfo->fpmeta = NULL;
2584 : : }
2585 : :
2586 : : /* Remove invalidated entries from the list, too */
1464 drowley@postgresql.o 2587 : 2170 : dclist_delete_from(&ri_constraint_cache_valid_list, iter.cur);
2588 : : }
2589 : : }
8636 tgl@sss.pgh.pa.us 2590 : 55072 : }
2591 : :
2592 : :
2593 : : /*
2594 : : * Prepare execution plan for a query to enforce an RI restriction
2595 : : */
2596 : : static SPIPlanPtr
128 peter@eisentraut.org 2597 :GNC 1360 : ri_PlanCheck(const char *querystr, int nargs, const Oid *argtypes,
2598 : : RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel)
2599 : : {
2600 : : SPIPlanPtr qplan;
2601 : : Relation query_rel;
2602 : : Oid save_userid;
2603 : : int save_sec_context;
2604 : :
2605 : : /*
2606 : : * Use the query type code to determine whether the query is run against
2607 : : * the PK or FK table; we'll do the check as that table's owner
2608 : : */
1673 alvherre@alvh.no-ip. 2609 [ + + ]:CBC 1360 : if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK)
2610 : 718 : query_rel = pk_rel;
2611 : : else
2612 : 642 : query_rel = fk_rel;
2613 : :
2614 : : /* Switch to proper UID to perform check as */
6175 tgl@sss.pgh.pa.us 2615 : 1360 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
2616 : 1360 : SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
2617 : : save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
2618 : : SECURITY_NOFORCE_RLS);
2619 : :
2620 : : /* Create the plan */
8594 2621 : 1360 : qplan = SPI_prepare(querystr, nargs, argtypes);
2622 : :
8431 2623 [ - + ]: 1360 : if (qplan == NULL)
3354 peter_e@gmx.net 2624 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare returned %s for %s", SPI_result_code_string(SPI_result), querystr);
2625 : :
2626 : : /* Restore UID and security context */
6175 tgl@sss.pgh.pa.us 2627 :CBC 1360 : SetUserIdAndSecContext(save_userid, save_sec_context);
2628 : :
2629 : : /* Save the plan */
2556 peter@eisentraut.org 2630 : 1360 : SPI_keepplan(qplan);
2631 : 1360 : ri_HashPreparedPlan(qkey, qplan);
2632 : :
8594 tgl@sss.pgh.pa.us 2633 : 1360 : return qplan;
2634 : : }
2635 : :
2636 : : /*
2637 : : * Perform a query to enforce an RI restriction
2638 : : */
2639 : : static bool
5253 2640 : 2769 : ri_PerformCheck(const RI_ConstraintInfo *riinfo,
2641 : : RI_QueryKey *qkey, SPIPlanPtr qplan,
2642 : : Relation fk_rel, Relation pk_rel,
2643 : : TupleTableSlot *oldslot, TupleTableSlot *newslot,
2644 : : bool is_restrict,
2645 : : bool detectNewRows, int expect_OK)
2646 : : {
2647 : : Relation query_rel,
2648 : : source_rel;
2649 : : bool source_is_pk;
2650 : : Snapshot test_snapshot;
2651 : : Snapshot crosscheck_snapshot;
2652 : : int limit;
2653 : : int spi_result;
2654 : : int exec_depth;
2655 : : Oid save_userid;
2656 : : int save_sec_context;
2657 : : Datum vals[RI_MAX_NUMKEYS * 2];
2658 : : char nulls[RI_MAX_NUMKEYS * 2];
2659 : :
2660 : : /*
2661 : : * Use the query type code to determine whether the query is run against
2662 : : * the PK or FK table; we'll do the check as that table's owner
2663 : : */
1673 alvherre@alvh.no-ip. 2664 [ + + ]: 2769 : if (qkey->constr_queryno <= RI_PLAN_LAST_ON_PK)
2665 : 1504 : query_rel = pk_rel;
2666 : : else
2667 : 1265 : query_rel = fk_rel;
2668 : :
2669 : : /*
2670 : : * The values for the query are taken from the table on which the trigger
2671 : : * is called - it is normally the other one with respect to query_rel. An
2672 : : * exception is ri_Check_Pk_Match(), which uses the PK table for both (and
2673 : : * sets queryno to RI_PLAN_CHECK_LOOKUPPK_FROM_PK). We might eventually
2674 : : * need some less klugy way to determine this.
2675 : : */
2676 [ + + ]: 2769 : if (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)
2677 : : {
2678 : 974 : source_rel = fk_rel;
2679 : 974 : source_is_pk = false;
2680 : : }
2681 : : else
2682 : : {
2683 : 1795 : source_rel = pk_rel;
2684 : 1795 : source_is_pk = true;
2685 : : }
2686 : :
2687 : : /* Extract the parameters to be passed into the query */
2807 peter@eisentraut.org 2688 [ + + ]: 2769 : if (newslot)
2689 : : {
1673 alvherre@alvh.no-ip. 2690 : 1118 : ri_ExtractValues(source_rel, newslot, riinfo, source_is_pk,
2691 : : vals, nulls);
2807 peter@eisentraut.org 2692 [ + + ]: 1118 : if (oldslot)
1673 alvherre@alvh.no-ip. 2693 : 144 : ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk,
5253 tgl@sss.pgh.pa.us 2694 : 144 : vals + riinfo->nkeys, nulls + riinfo->nkeys);
2695 : : }
2696 : : else
2697 : : {
1673 alvherre@alvh.no-ip. 2698 : 1651 : ri_ExtractValues(source_rel, oldslot, riinfo, source_is_pk,
2699 : : vals, nulls);
2700 : : }
2701 : :
2702 : : /*
2703 : : * In READ COMMITTED mode, we just need to use an up-to-date regular
2704 : : * snapshot, and we will see all rows that could be interesting. But in
2705 : : * transaction-snapshot mode, we can't change the transaction snapshot. If
2706 : : * the caller passes detectNewRows == false then it's okay to do the query
2707 : : * with the transaction snapshot; otherwise we use a current snapshot, and
2708 : : * tell the executor to error out if it finds any rows under the current
2709 : : * snapshot that wouldn't be visible per the transaction snapshot. Note
2710 : : * that SPI_execute_snapshot will register the snapshots, so we don't need
2711 : : * to bother here.
2712 : : */
5899 mail@joeconway.com 2713 [ + + + + ]: 2769 : if (IsolationUsesXactSnapshot() && detectNewRows)
2714 : : {
3424 tgl@sss.pgh.pa.us 2715 : 36 : CommandCounterIncrement(); /* be sure all my own work is visible */
6751 alvherre@alvh.no-ip. 2716 : 36 : test_snapshot = GetLatestSnapshot();
2717 : 36 : crosscheck_snapshot = GetTransactionSnapshot();
2718 : : }
2719 : : else
2720 : : {
2721 : : /* the default SPI behavior is okay */
8088 tgl@sss.pgh.pa.us 2722 : 2733 : test_snapshot = InvalidSnapshot;
2723 : 2733 : crosscheck_snapshot = InvalidSnapshot;
2724 : : }
2725 : :
2726 : : /*
2727 : : * If this is a select query (e.g., for a 'no action' or 'restrict'
2728 : : * trigger), we only need to see if there is a single row in the table,
2729 : : * matching the key. Otherwise, limit = 0 - because we want the query to
2730 : : * affect ALL the matching rows.
2731 : : */
8636 2732 : 2769 : limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
2733 : :
2734 : : /* Switch to proper UID to perform check as */
6175 2735 : 2769 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
2736 : 2769 : SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
2737 : : save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
2738 : : SECURITY_NOFORCE_RLS);
2739 : :
2740 : : /*
2741 : : * Finally we can run the query.
2742 : : *
2743 : : * Set fire_triggers to false to ensure that AFTER triggers are queued in
2744 : : * the outer query's after-trigger context and fire after all RI updates
2745 : : * on the same row are complete, rather than immediately.
2746 : : *
2747 : : * The plan is on the stack of executing plans while the query runs, so
2748 : : * that a nested check finding it invalid does not free it; see
2749 : : * ri_FetchPreparedPlan(). Pop it even if the query throws an error, so
2750 : : * that, whatever catches the error, the stack holds only the plans still
2751 : : * executing. We don't free anything in the finally block, so if the
2752 : : * query throws an error, whatever was queued while it ran stays queued.
2753 : : * The next check that finishes normally frees it, once no check is
2754 : : * executing it, or at the latest AtEOXact_RI() does.
2755 : : */
1 amitlan@postgresql.o 2756 : 2769 : exec_depth = ri_PushExecutingPlan(qplan);
2757 [ + + ]: 2769 : PG_TRY();
2758 : : {
2759 : 2769 : spi_result = SPI_execute_snapshot(qplan,
2760 : : vals, nulls,
2761 : : test_snapshot, crosscheck_snapshot,
2762 : : false, false, limit);
2763 : : }
2764 : 154 : PG_FINALLY();
2765 : : {
2766 : : /* Pop qplan, whether the query returned normally or threw an error. */
2767 : 2769 : ri_executing_depth = exec_depth;
2768 : : }
2769 [ + + ]: 2769 : PG_END_TRY();
2770 : :
2771 : : /* Restore UID and security context */
6175 tgl@sss.pgh.pa.us 2772 : 2615 : SetUserIdAndSecContext(save_userid, save_sec_context);
2773 : :
2774 : : /*
2775 : : * Free the queued plans that no check is executing any more, which may
2776 : : * include qplan itself now that it is popped. Checks further up the
2777 : : * stack may still be executing others, which stay queued.
2778 : : */
1 amitlan@postgresql.o 2779 [ + + ]: 2615 : if (ri_plan_dead_list != NIL)
2780 : 68 : ri_FreeDeadPlans();
2781 : :
2782 : : /* Check result */
8636 tgl@sss.pgh.pa.us 2783 [ - + ]: 2615 : if (spi_result < 0)
3354 peter_e@gmx.net 2784 [ # # ]:UBC 0 : elog(ERROR, "SPI_execute_snapshot returned %s", SPI_result_code_string(spi_result));
2785 : :
8636 tgl@sss.pgh.pa.us 2786 [ + - - + ]:CBC 2615 : if (expect_OK >= 0 && spi_result != expect_OK)
3354 peter_e@gmx.net 2787 [ # # ]:UBC 0 : ereport(ERROR,
2788 : : (errcode(ERRCODE_INTERNAL_ERROR),
2789 : : errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result",
2790 : : RelationGetRelationName(pk_rel),
2791 : : NameStr(riinfo->conname),
2792 : : RelationGetRelationName(fk_rel)),
2793 : : errhint("This is most likely due to a rule having rewritten the query.")));
2794 : :
2795 : : /* XXX wouldn't it be clearer to do this part at the caller? */
1673 alvherre@alvh.no-ip. 2796 [ + + + + ]:CBC 2615 : if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK &&
2797 : 1632 : expect_OK == SPI_OK_SELECT &&
2798 [ + + ]: 1632 : (SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK))
5253 tgl@sss.pgh.pa.us 2799 [ + + ]: 482 : ri_ReportViolation(riinfo,
2800 : : pk_rel, fk_rel,
2801 : : newslot ? newslot : oldslot,
2802 : : NULL,
2803 : : qkey->constr_queryno, is_restrict, false);
2804 : :
8636 2805 : 2133 : return SPI_processed != 0;
2806 : : }
2807 : :
2808 : : /*
2809 : : * ri_FastPathCheck
2810 : : * Perform per row FK existence check via direct index probe,
2811 : : * bypassing SPI.
2812 : : *
2813 : : * Return false if the index is unsuitable, so the caller can use SPI.
2814 : : * Otherwise, report any violation via ri_ReportViolation(), or return true
2815 : : * after a successful check.
2816 : : */
2817 : : static bool
197 amitlan@postgresql.o 2818 : 604750 : ri_FastPathCheck(RI_ConstraintInfo *riinfo,
2819 : : Relation fk_rel, TupleTableSlot *newslot)
2820 : : {
2821 : : Relation pk_rel;
2822 : : Relation idx_rel;
2823 : : IndexScanDesc scandesc;
2824 : : TupleTableSlot *slot;
2825 : : Datum pk_vals[INDEX_MAX_KEYS];
2826 : : char pk_nulls[INDEX_MAX_KEYS];
2827 : : ScanKeyData skey[INDEX_MAX_KEYS];
219 2828 : 604750 : bool found = false;
2829 : : Oid saved_userid;
2830 : : int saved_sec_context;
2831 : : Snapshot snapshot;
2832 : : FastPathMeta *fpmeta;
2833 : :
79 2834 : 604750 : INJECTION_POINT("ri-before-pk-lock", NULL);
2835 : :
219 2836 : 604750 : pk_rel = table_open(riinfo->pk_relid, RowShareLock);
2837 : :
2838 : : /*
2839 : : * The row lock taken below is refused in a read-only transaction unless
2840 : : * the referenced table is temporary, as ExecCheckXactReadOnly() refuses
2841 : : * the SPI path's SELECT ... FOR KEY SHARE.
2842 : : */
28 2843 [ + + + + ]: 604750 : if (XactReadOnly && !isTempNamespace(RelationGetNamespace(pk_rel)))
2844 : 4 : PreventCommandIfReadOnly("SELECT FOR KEY SHARE");
2845 : :
2846 : : /*
2847 : : * Advance the command counter so the check sees the effects of prior
2848 : : * triggers in this statement, as SPI does when executing the query issued
2849 : : * by ri_PerformCheck(). Do this after locking the referenced relation
2850 : : * and before reloading the constraint information, so local invalidations
2851 : : * are processed under the lock.
2852 : : */
56 2853 : 604746 : CommandCounterIncrement();
2854 : :
2855 : : /* Re-read the constraint under that lock. */
79 2856 : 604746 : riinfo = ri_LoadConstraintInfo(riinfo->constraint_id);
2857 : :
219 2858 : 604746 : idx_rel = index_open(riinfo->conindid, AccessShareLock);
2859 : :
21 2860 [ + + ]: 604746 : if (!ri_check_fastpath_index(riinfo, fk_rel, pk_rel, idx_rel))
2861 : : {
55 2862 : 68 : index_close(idx_rel, NoLock);
2863 : 68 : table_close(pk_rel, NoLock);
2864 : 68 : return false;
2865 : : }
2866 : :
2867 : : /*
2868 : : * Only now take the snapshot the scan will use. Acquiring it before
2869 : : * table_open() would let an unbounded amount of time pass while we wait
2870 : : * for the lock, during which another transaction can commit the very row
2871 : : * we are about to look for. The scan would not see it and the check
2872 : : * would report a violation for a key that exists.
2873 : : *
2874 : : * The SPI path does not have this problem: for this check it passes
2875 : : * InvalidSnapshot, so SPI takes the snapshot after the
2876 : : * referenced-relation lock has been acquired.
2877 : : *
2878 : : * Make this snapshot active too, as SPI does. STABLE equality functions
2879 : : * use the active snapshot, so leaving the outer query's snapshot active
2880 : : * could hide changes made by earlier triggers even though the index scan
2881 : : * can see them.
2882 : : */
56 2883 : 604678 : snapshot = RegisterSnapshot(GetTransactionSnapshot());
2884 : 604678 : PushActiveSnapshot(snapshot);
2885 : :
219 2886 : 604678 : slot = table_slot_create(pk_rel, NULL);
2887 : :
2888 : 604678 : GetUserIdAndSecContext(&saved_userid, &saved_sec_context);
2889 : 604678 : SetUserIdAndSecContext(RelationGetForm(pk_rel)->relowner,
2890 : : saved_sec_context |
2891 : : SECURITY_LOCAL_USERID_CHANGE |
2892 : : SECURITY_NOFORCE_RLS);
47 2893 : 604678 : ri_CheckPermissions(riinfo, pk_rel);
2894 : :
2895 : : /*
2896 : : * Begin the scan under the switched user id, so that any access method
2897 : : * code invoked by index_beginscan() runs as the PK relation's owner. For
2898 : : * btree this has no functional consequence, but it keeps the ordering
2899 : : * correct for out-of-tree access methods.
2900 : : */
51 pg@bowt.ie 2901 :GNC 604654 : scandesc = index_beginscan(pk_rel, idx_rel, false,
2902 : : snapshot, NULL,
2903 : : riinfo->nkeys, 0,
2904 : : SO_NONE);
2905 : :
218 amitlan@postgresql.o 2906 [ + + ]:CBC 604654 : if (riinfo->fpmeta == NULL)
2907 : : {
2908 : : /* Reload to ensure it's valid. */
2909 : 970 : riinfo = ri_LoadConstraintInfo(riinfo->constraint_id);
21 2910 : 970 : ri_populate_fastpath_metadata(riinfo, idx_rel);
2911 : : }
2912 : :
2913 : : /*
2914 : : * Use our own pointer to the metadata from here on. The permission
2915 : : * checks below look up catalog entries and so can process invalidation
2916 : : * messages, which detach riinfo->fpmeta (see
2917 : : * InvalidateConstraintCacheCallBack()); the detached object stays valid
2918 : : * until AtEOXact_RI().
2919 : : */
30 2920 : 604654 : fpmeta = riinfo->fpmeta;
2921 [ - + ]: 604654 : Assert(fpmeta);
2922 : 604654 : ri_CheckFunctionPermissions(riinfo, fpmeta);
219 2923 : 604650 : ri_ExtractValues(fk_rel, newslot, riinfo, false, pk_vals, pk_nulls);
21 2924 : 604650 : build_index_scankeys(riinfo, fpmeta, idx_rel, pk_vals, skey);
219 2925 : 604650 : found = ri_FastPathProbeOne(pk_rel, idx_rel, scandesc, slot,
2926 : : snapshot, riinfo, skey, riinfo->nkeys);
2927 : 604643 : SetUserIdAndSecContext(saved_userid, saved_sec_context);
2928 : 604643 : index_endscan(scandesc);
2929 : 604643 : ExecDropSingleTupleTableSlot(slot);
2930 : 604643 : UnregisterSnapshot(snapshot);
56 2931 : 604643 : PopActiveSnapshot();
2932 : :
219 2933 [ + + ]: 604643 : if (!found)
2934 : 297 : ri_ReportViolation(riinfo, pk_rel, fk_rel,
2935 : : newslot, NULL,
2936 : : RI_PLAN_CHECK_LOOKUPPK, false, false);
2937 : :
2938 : 604346 : index_close(idx_rel, NoLock);
2939 : 604346 : table_close(pk_rel, NoLock);
55 2940 : 604346 : return true;
2941 : : }
2942 : :
2943 : : /*
2944 : : * ri_FastPathProbeOne
2945 : : * Probe the PK index for one set of scan keys, lock the matching
2946 : : * tuple
2947 : : *
2948 : : * Returns true if a matching PK row was found, locked, and (if
2949 : : * applicable) visible to the transaction snapshot.
2950 : : */
2951 : : static bool
219 2952 : 604650 : ri_FastPathProbeOne(Relation pk_rel, Relation idx_rel,
2953 : : IndexScanDesc scandesc, TupleTableSlot *slot,
2954 : : Snapshot snapshot, const RI_ConstraintInfo *riinfo,
2955 : : ScanKeyData *skey, int nkeys)
2956 : : {
2957 : 604650 : bool found = false;
2958 : :
2959 : 604650 : index_rescan(scandesc, skey, nkeys, NULL, 0);
2960 : :
51 pg@bowt.ie 2961 [ + + ]:GNC 604650 : if (table_index_getnext_slot(scandesc, ForwardScanDirection, slot))
2962 : : {
2963 : : bool concurrently_updated;
2964 : :
219 amitlan@postgresql.o 2965 [ + + ]:CBC 604362 : if (ri_LockPKTuple(pk_rel, slot, snapshot,
2966 : : &concurrently_updated))
2967 : : {
2968 [ + + ]: 604350 : if (concurrently_updated)
210 2969 : 7 : found = recheck_matched_pk_tuple(idx_rel, skey, nkeys, slot);
2970 : : else
219 2971 : 604343 : found = true;
2972 : : }
2973 : : }
2974 : :
2975 : 604643 : return found;
2976 : : }
2977 : :
2978 : : /*
2979 : : * ri_LockPKTuple
2980 : : * Lock a PK tuple found by the fast-path index scan.
2981 : : *
2982 : : * Calls table_tuple_lock() directly with handling specific to RI checks.
2983 : : * Returns true if the tuple was successfully locked.
2984 : : *
2985 : : * Sets *concurrently_updated to true if the locked tuple was reached
2986 : : * by following an update chain (tmfd.traversed), indicating the caller
2987 : : * should recheck the key.
2988 : : */
2989 : : static bool
2990 : 604362 : ri_LockPKTuple(Relation pk_rel, TupleTableSlot *slot, Snapshot snap,
2991 : : bool *concurrently_updated)
2992 : : {
2993 : : TM_FailureData tmfd;
2994 : : TM_Result result;
2995 : 604362 : int lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS;
2996 : :
33 2997 : 604362 : *concurrently_updated = false;
2998 : :
219 2999 [ + + ]: 604362 : if (!IsolationUsesXactSnapshot())
3000 : 604340 : lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION;
3001 : :
3002 : : /*
3003 : : * Lock as of the command ID the scan's snapshot was taken with, as
3004 : : * ExecLockRows() uses the es_output_cid fixed when its query started.
3005 : : * User code run during the scan, such as an equality function, may have
3006 : : * advanced the current command ID since; with that, a row it updated
3007 : : * would look updated by an earlier command (TM_Invisible) rather than by
3008 : : * this one (TM_SelfModified).
3009 : : */
3010 : 604362 : result = table_tuple_lock(pk_rel, &slot->tts_tid, snap,
3011 : : slot, snap->curcid,
3012 : : LockTupleKeyShare, LockWaitBlock,
3013 : : lockflags, &tmfd);
3014 : :
3015 [ + + + + : 604359 : switch (result)
- - ]
3016 : : {
3017 : 604350 : case TM_Ok:
33 3018 [ + + ]: 604350 : if (tmfd.traversed)
219 3019 : 7 : *concurrently_updated = true;
3020 : 604350 : return true;
3021 : :
3022 : 4 : case TM_Deleted:
3023 [ + + ]: 4 : if (IsolationUsesXactSnapshot())
3024 [ + - ]: 3 : ereport(ERROR,
3025 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
3026 : : errmsg("could not serialize access due to concurrent delete")));
3027 : 1 : return false;
3028 : :
3029 : 1 : case TM_Updated:
3030 [ + - ]: 1 : if (IsolationUsesXactSnapshot())
3031 [ + - ]: 1 : ereport(ERROR,
3032 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
3033 : : errmsg("could not serialize access due to concurrent update")));
3034 : :
3035 : : /*
3036 : : * In READ COMMITTED, FIND_LAST_VERSION should have chased the
3037 : : * chain rather than returning TM_Updated. As in ExecLockRows(),
3038 : : * treat this as an unexpected result.
3039 : : */
219 amitlan@postgresql.o 3040 [ # # ]:UBC 0 : elog(ERROR, "unexpected table_tuple_lock status: %u", result);
3041 : : break;
3042 : :
219 amitlan@postgresql.o 3043 :CBC 4 : case TM_SelfModified:
3044 : :
3045 : : /*
3046 : : * As in ExecLockRows(), ignore a tuple updated or deleted by the
3047 : : * current command or a later command in this transaction.
3048 : : */
3049 : 4 : return false;
3050 : :
219 amitlan@postgresql.o 3051 :UBC 0 : case TM_Invisible:
3052 [ # # ]: 0 : elog(ERROR, "attempted to lock invisible tuple");
3053 : : break;
3054 : :
3055 : 0 : default:
3056 [ # # ]: 0 : elog(ERROR, "unrecognized table_tuple_lock status: %u", result);
3057 : : break;
3058 : : }
3059 : :
3060 : : return false; /* keep compiler quiet */
3061 : : }
3062 : :
3063 : : /*
3064 : : * Apply the cheap checks that depend only on riinfo; this runs for every
3065 : : * row. Checks that need the referenced index or catalog lookups are made
3066 : : * once by ri_check_fastpath_index(), which caches the result in
3067 : : * fastpath_state; an unknown state still needs it before using the fast
3068 : : * path.
3069 : : */
3070 : : static bool
219 amitlan@postgresql.o 3071 :CBC 605656 : ri_fastpath_is_applicable(const RI_ConstraintInfo *riinfo)
3072 : : {
3073 : : /*
3074 : : * Partitioned referenced tables are skipped for simplicity, since they
3075 : : * require routing the probe through the correct partition using
3076 : : * PartitionDirectory.
3077 : : */
3078 [ + + ]: 605656 : if (riinfo->pk_is_partitioned)
3079 : 587 : return false;
3080 : :
3081 : : /*
3082 : : * Temporal foreign keys use range overlap and containment semantics (&&,
3083 : : * <@, range_agg()) that inherently involve aggregation and multiple-row
3084 : : * reasoning, so they stay on the SPI path.
3085 : : */
3086 [ + + ]: 605069 : if (riinfo->hasperiod)
3087 : 151 : return false;
3088 : :
55 3089 : 604918 : return riinfo->fastpath_state != RI_FASTPATH_UNUSABLE;
3090 : : }
3091 : :
3092 : : /*
3093 : : * Check eligibility that depends on the index or on the FK column types
3094 : : * lazily, like fast-path scan metadata. Cache both success and failure until
3095 : : * the constraint information is reloaded.
3096 : : *
3097 : : * The caller has locked the referenced table, reloaded conindid, and opened
3098 : : * the index. Looking up index properties in ri_LoadConstraintInfo() would
3099 : : * race with REINDEX CONCURRENTLY dropping an index read before that lock.
3100 : : * The index-property checks use the held relation descriptors, and the check
3101 : : * of the FK column types uses fk_rel.
3102 : : */
3103 : : static bool
21 3104 : 604746 : ri_check_fastpath_index(RI_ConstraintInfo *riinfo, Relation fk_rel,
3105 : : Relation pk_rel, Relation idx_rel)
3106 : : {
3107 : : /* Opening the index can have processed further invalidations. */
55 3108 [ + + ]: 604746 : if (!riinfo->valid)
55 amitlan@postgresql.o 3109 :GBC 1 : riinfo = ri_LoadConstraintInfo(riinfo->constraint_id);
3110 : :
55 amitlan@postgresql.o 3111 [ + + ]:CBC 604746 : if (riinfo->fastpath_state != RI_FASTPATH_UNKNOWN)
3112 : 603700 : return riinfo->fastpath_state == RI_FASTPATH_USABLE;
3113 : :
3114 : : /*
3115 : : * The fast path probes the referenced index directly. A foreign key's
3116 : : * referenced index need not be a primary key; transformFkeyCheckAttrs()
3117 : : * accepts any unique index, so an out-of-tree amcanunique access method
3118 : : * could reach here. Restrict the fast path to btree; other access
3119 : : * methods fall back to SPI.
3120 : : */
3121 [ - + ]: 1046 : if (idx_rel->rd_rel->relam != BTREE_AM_OID)
3122 : : {
55 amitlan@postgresql.o 3123 :UBC 0 : riinfo->fastpath_state = RI_FASTPATH_UNUSABLE;
90 3124 : 0 : return false;
3125 : : }
3126 : :
3127 : : /*
3128 : : * Leave comparisons with a different index and referenced-column
3129 : : * collation to SPI. Map index keys to table attributes because the FK
3130 : : * columns need not be listed in index order. Ignore INCLUDE columns.
3131 : : */
55 amitlan@postgresql.o 3132 [ + + ]:CBC 2245 : for (int i = 0; i < idx_rel->rd_index->indnkeyatts; i++)
3133 : : {
3134 : 1215 : AttrNumber attnum = idx_rel->rd_index->indkey.values[i];
3135 : :
3136 [ + + ]: 1215 : if (idx_rel->rd_indcollation[i] != RIAttCollation(pk_rel, attnum))
3137 : : {
3138 : 16 : riinfo->fastpath_state = RI_FASTPATH_UNUSABLE;
3139 : 16 : return false;
3140 : : }
3141 : : }
3142 : :
3143 : : /*
3144 : : * The equality operator stored in pg_constraint must still be an equality
3145 : : * member of the index opfamily. When it is not, the direct fast-path
3146 : : * probe errors, so mark the fast path unusable and fall back to SPI,
3147 : : * which uses the same operator in a query where the planner simply
3148 : : * declines the index.
3149 : : */
47 3150 [ + + ]: 2221 : for (int i = 0; i < riinfo->nkeys; i++)
3151 : : {
3152 : : int idx_col;
3153 : :
3154 [ + - ]: 1392 : for (idx_col = 0; idx_col < idx_rel->rd_index->indnkeyatts; idx_col++)
3155 : : {
3156 : 1392 : if (idx_rel->rd_index->indkey.values[idx_col] ==
3157 [ + + ]: 1392 : riinfo->pk_attnums[i])
3158 : 1199 : break;
3159 : : }
3160 [ - + ]: 1199 : Assert(idx_col < idx_rel->rd_index->indnkeyatts);
3161 : :
3162 [ + + ]: 1199 : if (get_op_opfamily_strategy(riinfo->pf_eq_oprs[i],
3163 : 1199 : idx_rel->rd_opfamily[idx_col]) != BTEqualStrategyNumber)
3164 : : {
3165 : 8 : riinfo->fastpath_state = RI_FASTPATH_UNUSABLE;
3166 : 8 : return false;
3167 : : }
3168 : : }
3169 : :
3170 : : /*
3171 : : * The fast path passes FK values to the equality operator as they are, so
3172 : : * it needs the FK type to be binary-coercible to the operator's right
3173 : : * input type. That covers cross-type operators, which take the FK type
3174 : : * directly, domains, and binary-compatible types. Any other coercion is
3175 : : * left to SPI, which applies it as part of the query.
3176 : : */
21 3177 [ + + ]: 2169 : for (int i = 0; i < riinfo->nkeys; i++)
3178 : : {
3179 : 1191 : Oid fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
3180 : : Oid lefttype;
3181 : : Oid righttype;
3182 : :
3183 : 1191 : op_input_types(riinfo->pf_eq_oprs[i], &lefttype, &righttype);
3184 [ + + ]: 1191 : if (!IsBinaryCoercible(fk_type, righttype))
3185 : : {
3186 : 44 : riinfo->fastpath_state = RI_FASTPATH_UNUSABLE;
3187 : 44 : return false;
3188 : : }
3189 : : }
3190 : :
55 3191 : 978 : riinfo->fastpath_state = RI_FASTPATH_USABLE;
219 3192 : 978 : return true;
3193 : : }
3194 : :
3195 : : /*
3196 : : * ri_CheckPermissions
3197 : : * Check permissions for the SELECT ... FOR KEY SHARE used by the SPI
3198 : : * path, as the referenced table's owner.
3199 : : *
3200 : : * Go through ExecCheckPermissions() with a manufactured range table so that
3201 : : * ExecutorCheckPerms_hook gets control, as it does for the query the SPI
3202 : : * path executes.
3203 : : */
3204 : : static void
47 3205 : 604678 : ri_CheckPermissions(const RI_ConstraintInfo *riinfo, Relation query_rel)
3206 : : {
3207 : : AclResult aclresult;
3208 : 604678 : AclMode requiredPerms = ACL_SELECT | ACL_SELECT_FOR_UPDATE;
3209 : : RangeTblEntry *rte;
3210 : : RTEPermissionInfo *perminfo;
3211 : :
3212 : : /* USAGE on schema. */
219 3213 : 604678 : aclresult = object_aclcheck(NamespaceRelationId,
3214 : 604678 : RelationGetNamespace(query_rel),
3215 : : GetUserId(), ACL_USAGE);
3216 [ - + ]: 604678 : if (aclresult != ACLCHECK_OK)
219 amitlan@postgresql.o 3217 :UBC 0 : aclcheck_error(aclresult, OBJECT_SCHEMA,
3218 : 0 : get_namespace_name(RelationGetNamespace(query_rel)));
3219 : :
3220 : : /*
3221 : : * SELECT is needed only on the referenced key columns. FOR KEY SHARE
3222 : : * also needs UPDATE privilege, which may be granted on any column; leave
3223 : : * updatedCols empty as the SPI query does.
3224 : : */
47 amitlan@postgresql.o 3225 :CBC 604678 : perminfo = makeNode(RTEPermissionInfo);
3226 : 604678 : perminfo->relid = RelationGetRelid(query_rel);
3227 : 604678 : perminfo->requiredPerms = requiredPerms;
3228 [ + + ]: 1210256 : for (int i = 0; i < riinfo->nkeys; i++)
3229 : : {
3230 : 605578 : int attno = riinfo->pk_attnums[i] - FirstLowInvalidHeapAttributeNumber;
3231 : :
3232 : 605578 : perminfo->selectedCols = bms_add_member(perminfo->selectedCols, attno);
3233 : : }
3234 : :
42 3235 : 604678 : rte = makeNode(RangeTblEntry);
3236 : 604678 : rte->rtekind = RTE_RELATION;
3237 : 604678 : rte->relid = RelationGetRelid(query_rel);
3238 : 604678 : rte->relkind = query_rel->rd_rel->relkind;
3239 : 604678 : rte->rellockmode = RowShareLock;
3240 : 604678 : rte->perminfoindex = 1;
3241 : :
3242 : 604678 : (void) ExecCheckPermissions(list_make1(rte), list_make1(perminfo), true);
219 3243 : 604654 : }
3244 : :
3245 : : /*
3246 : : * recheck_matched_pk_tuple
3247 : : * After following an update chain (tmfd.traversed), verify that
3248 : : * the locked PK tuple still matches the original search keys.
3249 : : *
3250 : : * A non-key update (e.g. changing a non-PK column) creates a new tuple version
3251 : : * that we've now locked, but the key is unchanged -- that's fine. A key
3252 : : * update means the value we were looking for is gone, so we should treat it as
3253 : : * not found.
3254 : : */
3255 : : static bool
210 3256 : 7 : recheck_matched_pk_tuple(Relation idxrel, ScanKeyData *skeys, int nkeys,
3257 : : TupleTableSlot *new_slot)
3258 : : {
3259 : : /*
3260 : : * TODO: BuildIndexInfo does a syscache lookup + palloc on every call.
3261 : : * This only fires on the concurrent-update path (tmfd.traversed), which
3262 : : * should be rare, so the cost is acceptable for now. If profiling shows
3263 : : * otherwise, cache the IndexInfo in FastPathMeta.
3264 : : */
219 3265 : 7 : IndexInfo *indexInfo = BuildIndexInfo(idxrel);
3266 : : Datum values[INDEX_MAX_KEYS];
3267 : : bool isnull[INDEX_MAX_KEYS];
3268 : 7 : bool matched = true;
3269 : :
3270 : : /* PK indexes never have these. */
3271 [ + - - + ]: 7 : Assert(indexInfo->ii_Expressions == NIL &&
3272 : : indexInfo->ii_ExclusionOps == NULL);
3273 : :
3274 : : /* Form the index values and isnull flags given the table tuple. */
210 3275 [ - + ]: 7 : Assert(nkeys == indexInfo->ii_NumIndexKeyAttrs);
219 3276 : 7 : FormIndexDatum(indexInfo, new_slot, NULL, values, isnull);
210 3277 [ + + ]: 10 : for (int i = 0; i < nkeys; i++)
3278 : : {
219 3279 : 7 : ScanKeyData *skey = &skeys[i];
3280 : :
3281 : : /*
3282 : : * A foreign key may reference a nullable unique column, so the
3283 : : * version we chased the update chain to may have a NULL in a key
3284 : : * column. A NULL never equals the value we searched for, so treat it
3285 : : * as no match, as the SPI path's requalification would.
3286 : : */
90 3287 [ + + ]: 7 : if (isnull[i] ||
3288 [ + + ]: 5 : !DatumGetBool(FunctionCall2Coll(&skey->sk_func,
3289 : : skey->sk_collation,
3290 : : values[i],
3291 : : skey->sk_argument)))
3292 : : {
219 3293 : 4 : matched = false;
3294 : 4 : break;
3295 : : }
3296 : : }
3297 : :
3298 : 7 : return matched;
3299 : : }
3300 : :
3301 : : /*
3302 : : * ri_CheckFunctionPermissions
3303 : : * Check EXECUTE privilege on the functions the fast path invokes on the
3304 : : * FK values, as the referenced table's owner.
3305 : : *
3306 : : * This parallels the checks ExecInitFunc() performs when the SPI path
3307 : : * initializes its generated query, where the equality operator's function
3308 : : * appears in the WHERE clause. The fast path is not used when the FK values
3309 : : * need coercion (see ri_check_fastpath_index()), so that is the only function
3310 : : * to check. Call with the user id already switched to the referenced table's
3311 : : * owner.
3312 : : */
3313 : : static void
40 3314 : 604654 : ri_CheckFunctionPermissions(const RI_ConstraintInfo *riinfo,
3315 : : const FastPathMeta *fpmeta)
3316 : : {
3317 [ + + ]: 1210196 : for (int i = 0; i < riinfo->nkeys; i++)
3318 : : {
21 3319 : 605546 : Oid funcid = fpmeta->regops[i];
3320 : : AclResult aclresult;
3321 : :
3322 : 605546 : aclresult = object_aclcheck(ProcedureRelationId, funcid,
3323 : : GetUserId(), ACL_EXECUTE);
3324 [ + + ]: 605546 : if (aclresult != ACLCHECK_OK)
3325 : 4 : aclcheck_error(aclresult, OBJECT_FUNCTION,
3326 : 4 : get_func_name(funcid));
3327 [ - + ]: 605542 : InvokeFunctionExecuteHook(funcid);
3328 : : }
40 3329 : 604650 : }
3330 : :
3331 : : /*
3332 : : * build_index_scankeys
3333 : : * Build ScanKeys for a direct index probe of the PK's unique index.
3334 : : *
3335 : : * Uses operator procedures and strategy numbers cached by
3336 : : * ri_populate_fastpath_metadata() rather than looking them up on each
3337 : : * invocation. The FK values are used as they are: the fast path is only
3338 : : * used when they need no coercion to the operator's input type.
3339 : : */
3340 : : static void
219 3341 : 604650 : build_index_scankeys(const RI_ConstraintInfo *riinfo,
3342 : : const FastPathMeta *fpmeta,
3343 : : Relation idx_rel, const Datum *pk_vals,
3344 : : ScanKey skeys)
3345 : : {
3346 [ - + ]: 604650 : Assert(fpmeta);
3347 : :
3348 : : /*
3349 : : * Set up ScanKeys for the index scan. This is essentially how
3350 : : * ExecIndexBuildScanKeys() sets them up. Use the cached index_attnos and
3351 : : * the corresponding collation since FK columns may be in a different
3352 : : * order than PK index columns. Place each scan key at the array position
3353 : : * corresponding to its index column, since btree requires keys to be
3354 : : * ordered by attribute number.
3355 : : */
3356 [ + + ]: 1210192 : for (int i = 0; i < riinfo->nkeys; i++)
3357 : : {
209 3358 : 605542 : AttrNumber pkattrno = fpmeta->index_attnos[i];
3359 : 605542 : int skey_pos = pkattrno - 1; /* 0-based array position */
3360 : :
3361 : 605542 : ScanKeyEntryInitialize(&skeys[skey_pos], 0, pkattrno,
219 3362 : 605542 : fpmeta->strats[i], fpmeta->subtypes[i],
209 3363 : 605542 : idx_rel->rd_indcollation[skey_pos], fpmeta->regops[i],
219 3364 : 605542 : pk_vals[i]);
3365 : : }
3366 : 604650 : }
3367 : :
3368 : : /*
3369 : : * ri_populate_fastpath_metadata
3370 : : * Cache per-key metadata needed by build_index_scankeys().
3371 : : *
3372 : : * Looks up the operator procedure OID and index strategy/subtype for each
3373 : : * key column. Called lazily on first use and persists for the lifetime of
3374 : : * the RI_ConstraintInfo entry.
3375 : : */
3376 : : static void
21 3377 : 970 : ri_populate_fastpath_metadata(RI_ConstraintInfo *riinfo, Relation idx_rel)
3378 : : {
3379 : : FastPathMeta *fpmeta;
219 3380 : 970 : MemoryContext oldcxt = MemoryContextSwitchTo(TopMemoryContext);
3381 : :
3382 [ + - - + ]: 970 : Assert(riinfo != NULL && riinfo->valid);
78 3383 [ - + ]: 970 : Assert(riinfo->fpmeta == NULL);
3384 : :
219 3385 : 970 : fpmeta = palloc_object(FastPathMeta);
78 3386 : 970 : fpmeta->next_dead = NULL;
3387 : :
219 3388 [ + + ]: 2101 : for (int i = 0; i < riinfo->nkeys; i++)
3389 : : {
3390 : 1131 : Oid eq_opr = riinfo->pf_eq_oprs[i];
3391 : : Oid lefttype;
3392 : : int idx_col;
3393 : :
3394 : : /*
3395 : : * Find the index column position for this constraint key. The FK
3396 : : * constraint may reference columns in a different order than they
3397 : : * appear in the PK index, so we must map pk_attnums[i] to the
3398 : : * corresponding index column position.
3399 : : */
209 3400 [ + - ]: 1316 : for (idx_col = 0; idx_col < riinfo->nkeys; idx_col++)
3401 : : {
3402 [ + + ]: 1316 : if (idx_rel->rd_index->indkey.values[idx_col] == riinfo->pk_attnums[i])
3403 : 1131 : break;
3404 : : }
3405 [ - + ]: 1131 : Assert(idx_col < riinfo->nkeys);
3406 : :
3407 : : /* 1-based attribute number */
3408 : 1131 : fpmeta->index_attnos[i] = idx_col + 1;
3409 : :
219 3410 : 1131 : fpmeta->regops[i] = get_opcode(eq_opr);
3411 : :
3412 : 1131 : get_op_opfamily_properties(eq_opr,
209 3413 : 1131 : idx_rel->rd_opfamily[idx_col],
3414 : : false,
3415 : : &fpmeta->strats[i],
3416 : : &lefttype,
3417 : : &fpmeta->subtypes[i]);
3418 : : }
3419 : :
219 3420 : 970 : riinfo->fpmeta = fpmeta;
3421 : 970 : MemoryContextSwitchTo(oldcxt);
3422 : 970 : }
3423 : :
3424 : : /*
3425 : : * Extract fields from a tuple into Datum/nulls arrays
3426 : : */
3427 : : static void
2809 andres@anarazel.de 3428 : 607563 : ri_ExtractValues(Relation rel, TupleTableSlot *slot,
3429 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk,
3430 : : Datum *vals, char *nulls)
3431 : : {
3432 : : const int16 *attnums;
3433 : : bool isnull;
3434 : :
5253 tgl@sss.pgh.pa.us 3435 [ + + ]: 607563 : if (rel_is_pk)
3436 : 1939 : attnums = riinfo->pk_attnums;
3437 : : else
3438 : 605624 : attnums = riinfo->fk_attnums;
3439 : :
2807 peter@eisentraut.org 3440 [ + + ]: 1217139 : for (int i = 0; i < riinfo->nkeys; i++)
3441 : : {
2809 andres@anarazel.de 3442 : 609576 : vals[i] = slot_getattr(slot, attnums[i], &isnull);
8636 tgl@sss.pgh.pa.us 3443 [ - + ]: 609576 : nulls[i] = isnull ? 'n' : ' ';
3444 : : }
3445 : 607563 : }
3446 : :
3447 : : /*
3448 : : * Produce an error report
3449 : : *
3450 : : * If the failed constraint was on insert/update to the FK table,
3451 : : * we want the key names and values extracted from there, and the error
3452 : : * message to look like 'key blah is not present in PK'.
3453 : : * Otherwise, the attr names and values come from the PK table and the
3454 : : * message looks like 'key blah is still referenced from FK'.
3455 : : */
3456 : : static void
5253 3457 : 840 : ri_ReportViolation(const RI_ConstraintInfo *riinfo,
3458 : : Relation pk_rel, Relation fk_rel,
3459 : : TupleTableSlot *violatorslot, TupleDesc tupdesc,
3460 : : int queryno, bool is_restrict, bool partgone)
3461 : : {
3462 : : StringInfoData key_names;
3463 : : StringInfoData key_values;
3464 : : bool onfk;
3465 : : const int16 *attnums;
3466 : : Oid rel_oid;
3467 : : AclResult aclresult;
4315 sfrost@snowman.net 3468 : 840 : bool has_perm = true;
3469 : :
3470 : : /*
3471 : : * Determine which relation to complain about. If tupdesc wasn't passed
3472 : : * by caller, assume the violator tuple came from there.
3473 : : */
1673 alvherre@alvh.no-ip. 3474 : 840 : onfk = (queryno == RI_PLAN_CHECK_LOOKUPPK);
3475 [ + + ]: 840 : if (onfk)
3476 : : {
5253 tgl@sss.pgh.pa.us 3477 : 504 : attnums = riinfo->fk_attnums;
4315 sfrost@snowman.net 3478 : 504 : rel_oid = fk_rel->rd_id;
8431 tgl@sss.pgh.pa.us 3479 [ + + ]: 504 : if (tupdesc == NULL)
3480 : 465 : tupdesc = fk_rel->rd_att;
3481 : : }
3482 : : else
3483 : : {
5253 3484 : 336 : attnums = riinfo->pk_attnums;
4315 sfrost@snowman.net 3485 : 336 : rel_oid = pk_rel->rd_id;
8431 tgl@sss.pgh.pa.us 3486 [ + + ]: 336 : if (tupdesc == NULL)
3487 : 314 : tupdesc = pk_rel->rd_att;
3488 : : }
3489 : :
3490 : : /*
3491 : : * Check permissions- if the user does not have access to view the data in
3492 : : * any of the key columns then we don't include the errdetail() below.
3493 : : *
3494 : : * Check if RLS is enabled on the relation first. If so, we don't return
3495 : : * any specifics to avoid leaking data.
3496 : : *
3497 : : * Check table-level permissions next and, failing that, column-level
3498 : : * privileges.
3499 : : *
3500 : : * When a partition at the referenced side is being detached/dropped, we
3501 : : * needn't check, since the user must be the table owner anyway.
3502 : : */
2773 alvherre@alvh.no-ip. 3503 [ + + ]: 840 : if (partgone)
3504 : 22 : has_perm = true;
3505 [ + + ]: 818 : else if (check_enable_rls(rel_oid, InvalidOid, true) != RLS_ENABLED)
3506 : : {
4315 sfrost@snowman.net 3507 : 814 : aclresult = pg_class_aclcheck(rel_oid, GetUserId(), ACL_SELECT);
3508 [ - + ]: 814 : if (aclresult != ACLCHECK_OK)
3509 : : {
3510 : : /* Try for column-level permissions */
2807 peter@eisentraut.org 3511 [ # # ]:UBC 0 : for (int idx = 0; idx < riinfo->nkeys; idx++)
3512 : : {
4315 sfrost@snowman.net 3513 : 0 : aclresult = pg_attribute_aclcheck(rel_oid, attnums[idx],
3514 : : GetUserId(),
3515 : : ACL_SELECT);
3516 : :
3517 : : /* No access to the key */
3518 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
3519 : : {
3520 : 0 : has_perm = false;
3521 : 0 : break;
3522 : : }
3523 : : }
3524 : : }
3525 : : }
3526 : : else
4118 mail@joeconway.com 3527 :CBC 4 : has_perm = false;
3528 : :
4315 sfrost@snowman.net 3529 [ + + ]: 840 : if (has_perm)
3530 : : {
3531 : : /* Get printable versions of the keys involved */
3532 : 836 : initStringInfo(&key_names);
3533 : 836 : initStringInfo(&key_values);
2807 peter@eisentraut.org 3534 [ + + ]: 2047 : for (int idx = 0; idx < riinfo->nkeys; idx++)
3535 : : {
4315 sfrost@snowman.net 3536 : 1211 : int fnum = attnums[idx];
2809 andres@anarazel.de 3537 : 1211 : Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1);
3538 : : char *name,
3539 : : *val;
3540 : : Datum datum;
3541 : : bool isnull;
3542 : :
3543 : 1211 : name = NameStr(att->attname);
3544 : :
3545 : 1211 : datum = slot_getattr(violatorslot, fnum, &isnull);
3546 [ + - ]: 1211 : if (!isnull)
3547 : : {
3548 : : Oid foutoid;
3549 : : bool typisvarlena;
3550 : :
3551 : 1211 : getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena);
3552 : 1211 : val = OidOutputFunctionCall(foutoid, datum);
3553 : : }
3554 : : else
4315 sfrost@snowman.net 3555 :UBC 0 : val = "null";
3556 : :
4315 sfrost@snowman.net 3557 [ + + ]:CBC 1211 : if (idx > 0)
3558 : : {
3559 : 375 : appendStringInfoString(&key_names, ", ");
3560 : 375 : appendStringInfoString(&key_values, ", ");
3561 : : }
3562 : 1211 : appendStringInfoString(&key_names, name);
3563 : 1211 : appendStringInfoString(&key_values, val);
3564 : : }
3565 : : }
3566 : :
2773 alvherre@alvh.no-ip. 3567 [ + + ]: 840 : if (partgone)
3568 [ + - ]: 22 : ereport(ERROR,
3569 : : (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
3570 : : errmsg("removing partition \"%s\" violates foreign key constraint \"%s\"",
3571 : : RelationGetRelationName(pk_rel),
3572 : : NameStr(riinfo->conname)),
3573 : : errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
3574 : : key_names.data, key_values.data,
3575 : : RelationGetRelationName(fk_rel)),
3576 : : errtableconstraint(fk_rel, NameStr(riinfo->conname))));
1673 3577 [ + + ]: 818 : else if (onfk)
8494 bruce@momjian.us 3578 [ + - + - ]: 504 : ereport(ERROR,
3579 : : (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
3580 : : errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
3581 : : RelationGetRelationName(fk_rel),
3582 : : NameStr(riinfo->conname)),
3583 : : has_perm ?
3584 : : errdetail("Key (%s)=(%s) is not present in table \"%s\".",
3585 : : key_names.data, key_values.data,
3586 : : RelationGetRelationName(pk_rel)) :
3587 : : errdetail("Key is not present in table \"%s\".",
3588 : : RelationGetRelationName(pk_rel)),
3589 : : errtableconstraint(fk_rel, NameStr(riinfo->conname))));
703 peter@eisentraut.org 3590 [ + + ]: 314 : else if (is_restrict)
3591 [ + - + - ]: 20 : ereport(ERROR,
3592 : : (errcode(ERRCODE_RESTRICT_VIOLATION),
3593 : : errmsg("update or delete on table \"%s\" violates RESTRICT setting of foreign key constraint \"%s\" on table \"%s\"",
3594 : : RelationGetRelationName(pk_rel),
3595 : : NameStr(riinfo->conname),
3596 : : RelationGetRelationName(fk_rel)),
3597 : : has_perm ?
3598 : : errdetail("Key (%s)=(%s) is referenced from table \"%s\".",
3599 : : key_names.data, key_values.data,
3600 : : RelationGetRelationName(fk_rel)) :
3601 : : errdetail("Key is referenced from table \"%s\".",
3602 : : RelationGetRelationName(fk_rel)),
3603 : : errtableconstraint(fk_rel, NameStr(riinfo->conname))));
3604 : : else
8494 bruce@momjian.us 3605 [ + - + + ]: 294 : ereport(ERROR,
3606 : : (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
3607 : : errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"",
3608 : : RelationGetRelationName(pk_rel),
3609 : : NameStr(riinfo->conname),
3610 : : RelationGetRelationName(fk_rel)),
3611 : : has_perm ?
3612 : : errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
3613 : : key_names.data, key_values.data,
3614 : : RelationGetRelationName(fk_rel)) :
3615 : : errdetail("Key is still referenced from table \"%s\".",
3616 : : RelationGetRelationName(fk_rel)),
3617 : : errtableconstraint(fk_rel, NameStr(riinfo->conname))));
3618 : : }
3619 : :
3620 : :
3621 : : /*
3622 : : * ri_NullCheck -
3623 : : *
3624 : : * Determine the NULL state of all key values in a tuple
3625 : : *
3626 : : * Returns one of RI_KEYS_ALL_NULL, RI_KEYS_NONE_NULL or RI_KEYS_SOME_NULL.
3627 : : */
3628 : : static int
3144 andrew@dunslane.net 3629 : 608734 : ri_NullCheck(TupleDesc tupDesc,
3630 : : TupleTableSlot *slot,
3631 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk)
3632 : : {
3633 : : const int16 *attnums;
9703 bruce@momjian.us 3634 : 608734 : bool allnull = true;
3635 : 608734 : bool nonenull = true;
3636 : :
5253 tgl@sss.pgh.pa.us 3637 [ + + ]: 608734 : if (rel_is_pk)
3638 : 2170 : attnums = riinfo->pk_attnums;
3639 : : else
3640 : 606564 : attnums = riinfo->fk_attnums;
3641 : :
2807 peter@eisentraut.org 3642 [ + + ]: 1219789 : for (int i = 0; i < riinfo->nkeys; i++)
3643 : : {
2809 andres@anarazel.de 3644 [ + + ]: 611055 : if (slot_attisnull(slot, attnums[i]))
9890 JanWieck@Yahoo.com 3645 : 382 : nonenull = false;
3646 : : else
3647 : 610673 : allnull = false;
3648 : : }
3649 : :
3650 [ + + ]: 608734 : if (allnull)
3651 : 198 : return RI_KEYS_ALL_NULL;
3652 : :
3653 [ + + ]: 608536 : if (nonenull)
3654 : 608400 : return RI_KEYS_NONE_NULL;
3655 : :
3656 : 136 : return RI_KEYS_SOME_NULL;
3657 : : }
3658 : :
3659 : :
3660 : : /*
3661 : : * ri_InitHashTables -
3662 : : *
3663 : : * Initialize our internal hash tables.
3664 : : */
3665 : : static void
4069 tgl@sss.pgh.pa.us 3666 : 258 : ri_InitHashTables(void)
3667 : : {
3668 : : HASHCTL ctl;
3669 : :
5251 3670 : 258 : ctl.keysize = sizeof(Oid);
3671 : 258 : ctl.entrysize = sizeof(RI_ConstraintInfo);
3672 : 258 : ri_constraint_cache = hash_create("RI constraint cache",
3673 : : RI_INIT_CONSTRAINTHASHSIZE,
3674 : : &ctl, HASH_ELEM | HASH_BLOBS);
3675 : :
3676 : : /* Arrange to flush cache on pg_constraint, pg_amop or pg_cast changes */
3677 : 258 : CacheRegisterSyscacheCallback(CONSTROID,
3678 : : InvalidateConstraintCacheCallBack,
3679 : : (Datum) 0);
47 amitlan@postgresql.o 3680 : 258 : CacheRegisterSyscacheCallback(AMOPOPID,
3681 : : InvalidateConstraintCacheCallBack,
3682 : : (Datum) 0);
21 3683 : 258 : CacheRegisterSyscacheCallback(CASTSOURCETARGET,
3684 : : InvalidateConstraintCacheCallBack,
3685 : : (Datum) 0);
3686 : :
9703 bruce@momjian.us 3687 : 258 : ctl.keysize = sizeof(RI_QueryKey);
9166 tgl@sss.pgh.pa.us 3688 : 258 : ctl.entrysize = sizeof(RI_QueryHashEntry);
5251 3689 : 258 : ri_query_cache = hash_create("RI query cache",
3690 : : RI_INIT_QUERYHASHSIZE,
3691 : : &ctl, HASH_ELEM | HASH_BLOBS);
3692 : :
7204 3693 : 258 : ctl.keysize = sizeof(RI_CompareKey);
3694 : 258 : ctl.entrysize = sizeof(RI_CompareHashEntry);
5251 3695 : 258 : ri_compare_cache = hash_create("RI compare cache",
3696 : : RI_INIT_QUERYHASHSIZE,
3697 : : &ctl, HASH_ELEM | HASH_BLOBS);
9890 JanWieck@Yahoo.com 3698 : 258 : }
3699 : :
3700 : :
3701 : : /*
3702 : : * ri_FetchPreparedPlan -
3703 : : *
3704 : : * Lookup for a query key in our private hash table of prepared
3705 : : * and saved SPI execution plans. Return the plan if found or NULL.
3706 : : */
3707 : : static SPIPlanPtr
3708 : 2769 : ri_FetchPreparedPlan(RI_QueryKey *key)
3709 : : {
3710 : : RI_QueryHashEntry *entry;
3711 : : SPIPlanPtr plan;
3712 : :
3713 : : /*
3714 : : * On the first call initialize the hashtable
3715 : : */
3716 [ - + ]: 2769 : if (!ri_query_cache)
9890 JanWieck@Yahoo.com 3717 :UBC 0 : ri_InitHashTables();
3718 : :
3719 : : /*
3720 : : * Lookup for the key
3721 : : */
9703 bruce@momjian.us 3722 :CBC 2769 : entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
3723 : : key,
3724 : : HASH_FIND, NULL);
9890 JanWieck@Yahoo.com 3725 [ + + ]: 2769 : if (entry == NULL)
3726 : 1094 : return NULL;
3727 : :
3728 : : /*
3729 : : * Check whether the plan is still valid. If it isn't, we don't want to
3730 : : * simply rely on plancache.c to regenerate it; rather we should start
3731 : : * from scratch and rebuild the query text too. This is to cover cases
3732 : : * such as table/column renames. We depend on the plancache machinery to
3733 : : * detect possible invalidations, though.
3734 : : *
3735 : : * CAUTION: this check is only trustworthy if the caller has already
3736 : : * locked both FK and PK rels.
3737 : : */
6625 tgl@sss.pgh.pa.us 3738 : 1675 : plan = entry->plan;
3739 [ + - + + ]: 1675 : if (plan && SPI_plan_is_valid(plan))
3740 : 1409 : return plan;
3741 : :
3742 : : /*
3743 : : * Otherwise, detach the stale plan from the entry so that the caller's
3744 : : * replacement can be hashed under the same key, and free it if we can.
3745 : : *
3746 : : * We can't if an RI check further up the stack is executing this very
3747 : : * plan. The check's query can run user-defined code (a cast function
3748 : : * applied to the FK values, or a trigger fired by a cascaded update or
3749 : : * delete, say) whose DML fires another RI check for the same constraint,
3750 : : * and hence the same query key, before the outer SPI_execute_snapshot()
3751 : : * returns. If the plan has been invalidated in between, freeing it here
3752 : : * would pull the SPIPlan out from under the outer _SPI_execute_plan(). So
3753 : : * in that case queue it on ri_plan_dead_list, to be freed once no check
3754 : : * is executing it any more.
3755 : : */
3756 : 266 : entry->plan = NULL;
3757 [ + - ]: 266 : if (plan)
3758 : : {
1 amitlan@postgresql.o 3759 [ + + ]: 266 : if (!ri_PlanIsExecuting(plan))
3760 : 182 : SPI_freeplan(plan);
3761 : : else
3762 : : {
3763 : 84 : MemoryContext oldcxt = MemoryContextSwitchTo(TopMemoryContext);
3764 : :
3765 : 84 : ri_plan_dead_list = lappend(ri_plan_dead_list, plan);
3766 : 84 : MemoryContextSwitchTo(oldcxt);
3767 : : }
3768 : : }
3769 : :
6625 tgl@sss.pgh.pa.us 3770 : 266 : return NULL;
3771 : : }
3772 : :
3773 : :
3774 : : /*
3775 : : * ri_HashPreparedPlan -
3776 : : *
3777 : : * Add another plan to our private SPI query plan hashtable.
3778 : : */
3779 : : static void
7175 3780 : 1360 : ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
3781 : : {
3782 : : RI_QueryHashEntry *entry;
3783 : : bool found;
3784 : :
3785 : : /*
3786 : : * On the first call initialize the hashtable
3787 : : */
9890 JanWieck@Yahoo.com 3788 [ - + ]: 1360 : if (!ri_query_cache)
9890 JanWieck@Yahoo.com 3789 :UBC 0 : ri_InitHashTables();
3790 : :
3791 : : /*
3792 : : * Add the new plan. We might be overwriting an entry previously found
3793 : : * invalid by ri_FetchPreparedPlan.
3794 : : */
9703 bruce@momjian.us 3795 :CBC 1360 : entry = (RI_QueryHashEntry *) hash_search(ri_query_cache,
3796 : : key,
3797 : : HASH_ENTER, &found);
6625 tgl@sss.pgh.pa.us 3798 [ + + - + ]: 1360 : Assert(!found || entry->plan == NULL);
9890 JanWieck@Yahoo.com 3799 : 1360 : entry->plan = plan;
3800 : 1360 : }
3801 : :
3802 : :
3803 : : /*
3804 : : * ri_PushExecutingPlan -
3805 : : *
3806 : : * Record that ri_PerformCheck() is about to execute plan. Returns the depth
3807 : : * of the stack before the push; the caller pops by setting
3808 : : * ri_executing_depth back to it when SPI_execute_snapshot() returns or
3809 : : * throws an error.
3810 : : */
3811 : : static int
1 amitlan@postgresql.o 3812 : 2769 : ri_PushExecutingPlan(SPIPlanPtr plan)
3813 : : {
3814 : 2769 : int depth = ri_executing_depth;
3815 : :
3816 [ + + ]: 2769 : if (ri_executing_depth >= ri_executing_maxdepth)
3817 : : {
3818 : 169 : int newmax = Max(8, ri_executing_maxdepth * 2);
3819 : :
3820 [ + - ]: 169 : if (ri_executing_plans == NULL)
3821 : 169 : ri_executing_plans = (SPIPlanPtr *)
3822 : 169 : MemoryContextAlloc(TopMemoryContext,
3823 : : newmax * sizeof(SPIPlanPtr));
3824 : : else
1 amitlan@postgresql.o 3825 :UBC 0 : ri_executing_plans = (SPIPlanPtr *)
3826 : 0 : repalloc(ri_executing_plans, newmax * sizeof(SPIPlanPtr));
1 amitlan@postgresql.o 3827 :CBC 169 : ri_executing_maxdepth = newmax;
3828 : : }
3829 : 2769 : ri_executing_plans[ri_executing_depth++] = plan;
3830 : :
3831 : 2769 : return depth;
3832 : : }
3833 : :
3834 : : /*
3835 : : * ri_PlanIsExecuting -
3836 : : *
3837 : : * Is plan being executed by an ri_PerformCheck() call in progress? The
3838 : : * stack is as deep as RI checks nest, so a linear scan is fine.
3839 : : */
3840 : : static bool
3841 : 374 : ri_PlanIsExecuting(SPIPlanPtr plan)
3842 : : {
3843 [ + + ]: 374 : for (int i = 0; i < ri_executing_depth; i++)
3844 : : {
3845 [ + - ]: 108 : if (ri_executing_plans[i] == plan)
3846 : 108 : return true;
3847 : : }
3848 : 266 : return false;
3849 : : }
3850 : :
3851 : : /*
3852 : : * ri_FreeDeadPlans -
3853 : : *
3854 : : * Free the plans queued on ri_plan_dead_list that no RI check is executing
3855 : : * any more, keeping the others queued. With the stack empty, that is all of
3856 : : * them.
3857 : : */
3858 : : static void
3859 : 72 : ri_FreeDeadPlans(void)
3860 : : {
3861 : 72 : List *plans = ri_plan_dead_list;
3862 : : ListCell *lc;
3863 : :
3864 : : /*
3865 : : * Detach the list from the static before freeing, so that an error while
3866 : : * freeing a plan does not leave a dangling pointer to a half-freed plan
3867 : : * in it. Not expected, but we may be called from the abort path, where
3868 : : * no further cleanup pass would come to fix that up.
3869 : : */
3870 : 72 : ri_plan_dead_list = NIL;
3871 : :
3872 [ + + + + : 180 : foreach(lc, plans)
+ + ]
3873 : : {
3874 : 108 : SPIPlanPtr plan = (SPIPlanPtr) lfirst(lc);
3875 : :
3876 [ + + ]: 108 : if (ri_PlanIsExecuting(plan))
3877 : 24 : continue;
3878 : 84 : SPI_freeplan(plan);
3879 : 84 : plans = foreach_delete_current(plans, lc);
3880 : : }
3881 : :
3882 : : /* Put back whatever is still executing, if any. */
3883 [ + + - + ]: 72 : Assert(plans == NIL || ri_executing_depth > 0);
3884 : 72 : ri_plan_dead_list = plans;
3885 : 72 : }
3886 : :
3887 : :
3888 : : /*
3889 : : * ri_KeysEqual -
3890 : : *
3891 : : * Check if all key values in OLD and NEW are "equivalent":
3892 : : * For normal FKs we check for equality.
3893 : : * For temporal FKs we check that the PK side is a superset of its old value,
3894 : : * or the FK side is a subset of its old value.
3895 : : *
3896 : : * Note: at some point we might wish to redefine this as checking for
3897 : : * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
3898 : : * considered equal. Currently there is no need since all callers have
3899 : : * previously found at least one of the rows to contain no nulls.
3900 : : */
3901 : : static bool
2809 andres@anarazel.de 3902 : 1424 : ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
3903 : : const RI_ConstraintInfo *riinfo, bool rel_is_pk)
3904 : : {
3905 : : const int16 *attnums;
3906 : :
7204 tgl@sss.pgh.pa.us 3907 [ + + ]: 1424 : if (rel_is_pk)
3908 : 935 : attnums = riinfo->pk_attnums;
3909 : : else
3910 : 489 : attnums = riinfo->fk_attnums;
3911 : :
3912 : : /* XXX: could be worthwhile to fetch all necessary attrs at once */
2807 peter@eisentraut.org 3913 [ + + ]: 2202 : for (int i = 0; i < riinfo->nkeys; i++)
3914 : : {
3915 : : Datum oldvalue;
3916 : : Datum newvalue;
3917 : : bool isnull;
3918 : :
3919 : : /*
3920 : : * Get one attribute's oldvalue. If it is NULL - they're not equal.
3921 : : */
2809 andres@anarazel.de 3922 : 1648 : oldvalue = slot_getattr(oldslot, attnums[i], &isnull);
9890 JanWieck@Yahoo.com 3923 [ + + ]: 1648 : if (isnull)
3924 : 870 : return false;
3925 : :
3926 : : /*
3927 : : * Get one attribute's newvalue. If it is NULL - they're not equal.
3928 : : */
2809 andres@anarazel.de 3929 : 1630 : newvalue = slot_getattr(newslot, attnums[i], &isnull);
9890 JanWieck@Yahoo.com 3930 [ + + ]: 1630 : if (isnull)
3931 : 2 : return false;
3932 : :
2789 peter@eisentraut.org 3933 [ + + ]: 1628 : if (rel_is_pk)
3934 : : {
3935 : : /*
3936 : : * If we are looking at the PK table, then do a bytewise
3937 : : * comparison. We must propagate PK changes if the value is
3938 : : * changed to one that "looks" different but would compare as
3939 : : * equal using the equality operator. This only makes a
3940 : : * difference for ON UPDATE CASCADE, but for consistency we treat
3941 : : * all changes to the PK the same.
3942 : : */
685 drowley@postgresql.o 3943 : 1101 : CompactAttribute *att = TupleDescCompactAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
3944 : :
2789 peter@eisentraut.org 3945 [ + + ]: 1101 : if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
3946 : 641 : return false;
3947 : : }
3948 : : else
3949 : : {
3950 : : Oid eq_opr;
3951 : :
3952 : : /*
3953 : : * When comparing the PERIOD columns we can skip the check
3954 : : * whenever the referencing column stayed equal or shrank, so test
3955 : : * with the contained-by operator instead.
3956 : : */
779 3957 [ + + + + ]: 527 : if (riinfo->hasperiod && i == riinfo->nkeys - 1)
3958 : 32 : eq_opr = riinfo->period_contained_by_oper;
3959 : : else
3960 : 495 : eq_opr = riinfo->ff_eq_oprs[i];
3961 : :
3962 : : /*
3963 : : * For the FK table, compare with the appropriate equality
3964 : : * operator. Changes that compare equal will still satisfy the
3965 : : * constraint after the update.
3966 : : */
720 3967 [ + + ]: 527 : if (!ri_CompareWithCast(eq_opr, RIAttType(rel, attnums[i]), RIAttCollation(rel, attnums[i]),
3968 : : newvalue, oldvalue))
2789 3969 : 209 : return false;
3970 : : }
3971 : : }
3972 : :
9890 JanWieck@Yahoo.com 3973 : 554 : return true;
3974 : : }
3975 : :
3976 : :
3977 : : /*
3978 : : * ri_CompareWithCast -
3979 : : *
3980 : : * Call the appropriate comparison operator for two values.
3981 : : * Normally this is equality, but for the PERIOD part of foreign keys
3982 : : * it is ContainedBy, so the order of lhs vs rhs is significant.
3983 : : * See below for how the collation is applied.
3984 : : *
3985 : : * NB: we have already checked that neither value is null.
3986 : : */
3987 : : static bool
720 peter@eisentraut.org 3988 : 527 : ri_CompareWithCast(Oid eq_opr, Oid typeid, Oid collid,
3989 : : Datum lhs, Datum rhs)
3990 : : {
7204 tgl@sss.pgh.pa.us 3991 : 527 : RI_CompareHashEntry *entry = ri_HashCompareOp(eq_opr, typeid);
3992 : :
3993 : : /* Do we need to cast the values? */
3994 [ + + ]: 527 : if (OidIsValid(entry->cast_func_finfo.fn_oid))
3995 : : {
779 peter@eisentraut.org 3996 : 8 : lhs = FunctionCall3(&entry->cast_func_finfo,
3997 : : lhs,
3998 : : Int32GetDatum(-1), /* typmod */
3999 : : BoolGetDatum(false)); /* implicit coercion */
4000 : 8 : rhs = FunctionCall3(&entry->cast_func_finfo,
4001 : : rhs,
4002 : : Int32GetDatum(-1), /* typmod */
4003 : : BoolGetDatum(false)); /* implicit coercion */
4004 : : }
4005 : :
4006 : : /*
4007 : : * Apply the comparison operator.
4008 : : *
4009 : : * Note: This function is part of a call stack that determines whether an
4010 : : * update to a row is significant enough that it needs checking or action
4011 : : * on the other side of a foreign-key constraint. Therefore, the
4012 : : * comparison here would need to be done with the collation of the *other*
4013 : : * table. For simplicity (e.g., we might not even have the other table
4014 : : * open), we'll use our own collation. This is fine because we require
4015 : : * that both collations have the same notion of equality (either they are
4016 : : * both deterministic or else they are both the same).
4017 : : *
4018 : : * With range/multirangetypes, the collation of the base type is stored as
4019 : : * part of the rangetype (pg_range.rngcollation), and always used, so
4020 : : * there is no danger of inconsistency even using a non-equals operator.
4021 : : * But if we support arbitrary types with PERIOD, we should perhaps just
4022 : : * always force a re-check.
4023 : : */
720 4024 : 527 : return DatumGetBool(FunctionCall2Coll(&entry->eq_opr_finfo, collid, lhs, rhs));
4025 : : }
4026 : :
4027 : : /*
4028 : : * ri_HashCompareOp -
4029 : : *
4030 : : * See if we know how to compare two values, and create a new hash entry
4031 : : * if not.
4032 : : */
4033 : : static RI_CompareHashEntry *
7204 tgl@sss.pgh.pa.us 4034 : 527 : ri_HashCompareOp(Oid eq_opr, Oid typeid)
4035 : : {
4036 : : RI_CompareKey key;
4037 : : RI_CompareHashEntry *entry;
4038 : : bool found;
4039 : :
4040 : : /*
4041 : : * On the first call initialize the hashtable
4042 : : */
4043 [ - + ]: 527 : if (!ri_compare_cache)
7204 tgl@sss.pgh.pa.us 4044 :UBC 0 : ri_InitHashTables();
4045 : :
4046 : : /*
4047 : : * Find or create a hash entry. Note we're assuming RI_CompareKey
4048 : : * contains no struct padding.
4049 : : */
7204 tgl@sss.pgh.pa.us 4050 :CBC 527 : key.eq_opr = eq_opr;
4051 : 527 : key.typeid = typeid;
4052 : 527 : entry = (RI_CompareHashEntry *) hash_search(ri_compare_cache,
4053 : : &key,
4054 : : HASH_ENTER, &found);
4055 [ + + ]: 527 : if (!found)
4056 : 158 : entry->valid = false;
4057 : :
4058 : : /*
4059 : : * If not already initialized, do so. Since we'll keep this hash entry
4060 : : * for the life of the backend, put any subsidiary info for the function
4061 : : * cache structs into TopMemoryContext.
4062 : : */
4063 [ + + ]: 527 : if (!entry->valid)
4064 : : {
4065 : : Oid lefttype,
4066 : : righttype,
4067 : : castfunc;
4068 : : CoercionPathType pathtype;
4069 : :
4070 : : /* We always need to know how to call the equality operator */
4071 : 158 : fmgr_info_cxt(get_opcode(eq_opr), &entry->eq_opr_finfo,
4072 : : TopMemoryContext);
4073 : :
4074 : : /*
4075 : : * If we chose to use a cast from FK to PK type, we may have to apply
4076 : : * the cast function to get to the operator's input type.
4077 : : *
4078 : : * XXX eventually it would be good to support array-coercion cases
4079 : : * here and in ri_CompareWithCast(). At the moment there is no point
4080 : : * because cases involving nonidentical array types will be rejected
4081 : : * at constraint creation time.
4082 : : *
4083 : : * XXX perhaps also consider supporting CoerceViaIO? No need at the
4084 : : * moment since that will never be generated for implicit coercions.
4085 : : */
4086 : 158 : op_input_types(eq_opr, &lefttype, &righttype);
21 amitlan@postgresql.o 4087 [ - + ]: 158 : Assert(lefttype == righttype);
4088 [ + + ]: 158 : if (typeid == lefttype)
3424 tgl@sss.pgh.pa.us 4089 : 142 : castfunc = InvalidOid; /* simplest case */
4090 : : else
4091 : : {
7093 4092 : 16 : pathtype = find_coercion_pathway(lefttype, typeid,
4093 : : COERCION_IMPLICIT,
4094 : : &castfunc);
4095 [ + + + - ]: 16 : if (pathtype != COERCION_PATH_FUNC &&
4096 : : pathtype != COERCION_PATH_RELABELTYPE)
4097 : : {
4098 : : /*
4099 : : * The declared input type of the eq_opr might be a
4100 : : * polymorphic type such as ANYARRAY or ANYENUM, or other
4101 : : * special cases such as RECORD; find_coercion_pathway
4102 : : * currently doesn't subsume these special cases.
4103 : : */
4700 4104 [ - + ]: 12 : if (!IsBinaryCoercible(typeid, lefttype))
7093 tgl@sss.pgh.pa.us 4105 [ # # ]:UBC 0 : elog(ERROR, "no conversion function from %s to %s",
4106 : : format_type_be(typeid),
4107 : : format_type_be(lefttype));
4108 : : }
4109 : : }
7204 tgl@sss.pgh.pa.us 4110 [ + + ]:CBC 158 : if (OidIsValid(castfunc))
4111 : 4 : fmgr_info_cxt(castfunc, &entry->cast_func_finfo,
4112 : : TopMemoryContext);
4113 : : else
4114 : 154 : entry->cast_func_finfo.fn_oid = InvalidOid;
4115 : 158 : entry->valid = true;
4116 : : }
4117 : :
4118 : 527 : return entry;
4119 : : }
4120 : :
4121 : :
4122 : : /*
4123 : : * Given a trigger function OID, determine whether it is an RI trigger,
4124 : : * and if so whether it is attached to PK or FK relation.
4125 : : */
4126 : : int
7829 neilc@samurai.com 4127 : 5761 : RI_FKey_trigger_type(Oid tgfoid)
4128 : : {
4129 [ + + + ]: 5761 : switch (tgfoid)
4130 : : {
4131 : 2092 : case F_RI_FKEY_CASCADE_DEL:
4132 : : case F_RI_FKEY_CASCADE_UPD:
4133 : : case F_RI_FKEY_RESTRICT_DEL:
4134 : : case F_RI_FKEY_RESTRICT_UPD:
4135 : : case F_RI_FKEY_SETNULL_DEL:
4136 : : case F_RI_FKEY_SETNULL_UPD:
4137 : : case F_RI_FKEY_SETDEFAULT_DEL:
4138 : : case F_RI_FKEY_SETDEFAULT_UPD:
4139 : : case F_RI_FKEY_NOACTION_DEL:
4140 : : case F_RI_FKEY_NOACTION_UPD:
4141 : 2092 : return RI_TRIGGER_PK;
4142 : :
4143 : 1836 : case F_RI_FKEY_CHECK_INS:
4144 : : case F_RI_FKEY_CHECK_UPD:
4145 : 1836 : return RI_TRIGGER_FK;
4146 : : }
4147 : :
4148 : 1833 : return RI_TRIGGER_NONE;
4149 : : }
4150 : :
4151 : : /*
4152 : : * AtEOXact_RI
4153 : : * End-of-transaction cleanup for referential integrity.
4154 : : *
4155 : : * This releases objects that were detached from the RI caches during the
4156 : : * transaction but could not be freed on the spot, because an RI check
4157 : : * further up the stack might still have been using them:
4158 : : *
4159 : : * - fast-path metadata detached by InvalidateConstraintCacheCallBack(),
4160 : : * queued on ri_fpmeta_dead_list;
4161 : : *
4162 : : * - SPI plans found invalid by ri_FetchPreparedPlan() while a check was
4163 : : * executing them, queued on ri_plan_dead_list. These are normally freed
4164 : : * as soon as the check executing them finishes; we only see them here if
4165 : : * an error interrupted that check and no later check finished normally in
4166 : : * the transaction.
4167 : : *
4168 : : * Here no RI check can be in progress, so both can be released. The
4169 : : * release is the same on the commit and the abort path; isCommit only
4170 : : * decides whether we can assert that the stack of executing plans is empty.
4171 : : *
4172 : : * There is no AtEOSubXact_RI() counterpart. Nothing here is scoped to a
4173 : : * subtransaction: a detached object stays reachable from its dead list
4174 : : * whichever subtransaction detached it, and a check using one may be
4175 : : * running at an outer level, so releasing at subtransaction end would be
4176 : : * unsafe as well as unnecessary.
4177 : : */
4178 : : void
129 amitlan@postgresql.o 4179 : 430300 : AtEOXact_RI(bool isCommit)
4180 : : {
78 4181 [ + + ]: 431000 : while (ri_fpmeta_dead_list != NULL)
4182 : : {
4183 : 700 : FastPathMeta *dead = ri_fpmeta_dead_list;
4184 : :
4185 : 700 : ri_fpmeta_dead_list = dead->next_dead;
4186 : 700 : pfree(dead);
4187 : : }
4188 : :
4189 : : /*
4190 : : * No RI check can be in progress here, so clear the stack. On commit it
4191 : : * must be empty already, since ri_PerformCheck() pops its plan whether
4192 : : * the query returns or throws. On abort it may not be, because a FATAL
4193 : : * error skips those pops.
4194 : : */
1 4195 [ - + - - ]: 430300 : Assert(ri_executing_depth == 0 || !isCommit);
4196 : 430300 : ri_executing_depth = 0;
4197 [ + + ]: 430300 : if (ri_plan_dead_list != NIL)
4198 : 4 : ri_FreeDeadPlans();
216 4199 : 430300 : }
|