aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/utils/adt/ri_triggers.c67
-rw-r--r--src/test/regress/expected/foreign_key.out18
-rw-r--r--src/test/regress/sql/foreign_key.sql16
3 files changed, 97 insertions, 4 deletions
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 6e3a41062fc..09a2ad28814 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -101,7 +101,10 @@ typedef struct RI_ConstraintInfo
{
Oid constraint_id; /* OID of pg_constraint entry (hash key) */
bool valid; /* successfully initialized? */
- uint32 oidHashValue; /* hash value of pg_constraint OID */
+ Oid constraint_root_id; /* OID of topmost ancestor constraint;
+ * same as constraint_id if not inherited */
+ uint32 oidHashValue; /* hash value of constraint_id */
+ uint32 rootHashValue; /* hash value of constraint_root_id */
NameData conname; /* name of the FK constraint */
Oid pk_relid; /* referenced relation */
Oid fk_relid; /* referencing relation */
@@ -207,6 +210,7 @@ static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
static const RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
Relation trig_rel, bool rel_is_pk);
static const RI_ConstraintInfo *ri_LoadConstraintInfo(Oid constraintOid);
+static Oid get_ri_constraint_root(Oid constrOid);
static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes,
RI_QueryKey *qkey, Relation fk_rel, Relation pk_rel);
static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
@@ -1892,7 +1896,7 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
* Construct a hashtable key for a prepared SPI plan of an FK constraint.
*
* key: output argument, *key is filled in based on the other arguments
- * riinfo: info from pg_constraint entry
+ * riinfo: info derived from pg_constraint entry
* constr_queryno: an internal number identifying the query type
* (see RI_PLAN_XXX constants at head of file)
* ----------
@@ -1902,10 +1906,27 @@ ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo,
int32 constr_queryno)
{
/*
+ * Inherited constraints with a common ancestor can share ri_query_cache
+ * entries for all query types except RI_PLAN_CHECK_LOOKUPPK_FROM_PK.
+ * Except in that case, the query processes the other table involved in
+ * the FK constraint (i.e., not the table on which the trigger has been
+ * fired), and so it will be the same for all members of the inheritance
+ * tree. So we may use the root constraint's OID in the hash key, rather
+ * than the constraint's own OID. This avoids creating duplicate SPI
+ * plans, saving lots of work and memory when there are many partitions
+ * with similar FK constraints.
+ *
+ * (Note that we must still have a separate RI_ConstraintInfo for each
+ * constraint, because partitions can have different column orders,
+ * resulting in different pk_attnums[] or fk_attnums[] array contents.)
+ *
* We assume struct RI_QueryKey contains no padding bytes, else we'd need
* to use memset to clear them.
*/
- key->constr_id = riinfo->constraint_id;
+ if (constr_queryno != RI_PLAN_CHECK_LOOKUPPK_FROM_PK)
+ key->constr_id = riinfo->constraint_root_id;
+ else
+ key->constr_id = riinfo->constraint_id;
key->constr_queryno = constr_queryno;
}
@@ -2051,8 +2072,15 @@ ri_LoadConstraintInfo(Oid constraintOid)
/* And extract data */
Assert(riinfo->constraint_id == constraintOid);
+ if (OidIsValid(conForm->conparentid))
+ riinfo->constraint_root_id =
+ get_ri_constraint_root(conForm->conparentid);
+ else
+ riinfo->constraint_root_id = constraintOid;
riinfo->oidHashValue = GetSysCacheHashValue1(CONSTROID,
ObjectIdGetDatum(constraintOid));
+ riinfo->rootHashValue = GetSysCacheHashValue1(CONSTROID,
+ ObjectIdGetDatum(riinfo->constraint_root_id));
memcpy(&riinfo->conname, &conForm->conname, sizeof(NameData));
riinfo->pk_relid = conForm->confrelid;
riinfo->fk_relid = conForm->conrelid;
@@ -2083,6 +2111,30 @@ ri_LoadConstraintInfo(Oid constraintOid)
}
/*
+ * get_ri_constraint_root
+ * Returns the OID of the constraint's root parent
+ */
+static Oid
+get_ri_constraint_root(Oid constrOid)
+{
+ for (;;)
+ {
+ HeapTuple tuple;
+ Oid constrParentOid;
+
+ tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constrOid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for constraint %u", constrOid);
+ constrParentOid = ((Form_pg_constraint) GETSTRUCT(tuple))->conparentid;
+ ReleaseSysCache(tuple);
+ if (!OidIsValid(constrParentOid))
+ break; /* we reached the root constraint */
+ constrOid = constrParentOid;
+ }
+ return constrOid;
+}
+
+/*
* Callback for pg_constraint inval events
*
* While most syscache callbacks just flush all their entries, pg_constraint
@@ -2117,7 +2169,14 @@ InvalidateConstraintCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
RI_ConstraintInfo *riinfo = dlist_container(RI_ConstraintInfo,
valid_link, iter.cur);
- if (hashvalue == 0 || riinfo->oidHashValue == hashvalue)
+ /*
+ * We must invalidate not only entries directly matching the given
+ * hash value, but also child entries, in case the invalidation
+ * affects a root constraint.
+ */
+ if (hashvalue == 0 ||
+ riinfo->oidHashValue == hashvalue ||
+ riinfo->rootHashValue == hashvalue)
{
riinfo->valid = false;
/* Remove invalidated entries from the list, too */
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 07bd5b6434f..7386f4d6359 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -2470,3 +2470,21 @@ DROP SCHEMA fkpart9 CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table fkpart9.pk
drop cascades to table fkpart9.fk
+-- test that ri_Check_Pk_Match() scans the correct partition for a deferred
+-- ON DELETE/UPDATE NO ACTION constraint
+CREATE SCHEMA fkpart10
+ CREATE TABLE tbl1(f1 int PRIMARY KEY) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl1_p1 PARTITION OF tbl1 FOR VALUES FROM (minvalue) TO (1)
+ CREATE TABLE tbl1_p2 PARTITION OF tbl1 FOR VALUES FROM (1) TO (maxvalue)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED);
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+INSERT INTO fkpart10.tbl2 VALUES (0), (1);
+BEGIN;
+DELETE FROM fkpart10.tbl1 WHERE f1 = 0;
+UPDATE fkpart10.tbl1 SET f1 = 2 WHERE f1 = 1;
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+COMMIT;
+DROP SCHEMA fkpart10 CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table fkpart10.tbl1
+drop cascades to table fkpart10.tbl2
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index c5c9011afcb..67aa20435d2 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -1738,3 +1738,19 @@ DELETE FROM fkpart9.pk WHERE a=35;
SELECT * FROM fkpart9.pk;
SELECT * FROM fkpart9.fk;
DROP SCHEMA fkpart9 CASCADE;
+
+-- test that ri_Check_Pk_Match() scans the correct partition for a deferred
+-- ON DELETE/UPDATE NO ACTION constraint
+CREATE SCHEMA fkpart10
+ CREATE TABLE tbl1(f1 int PRIMARY KEY) PARTITION BY RANGE(f1)
+ CREATE TABLE tbl1_p1 PARTITION OF tbl1 FOR VALUES FROM (minvalue) TO (1)
+ CREATE TABLE tbl1_p2 PARTITION OF tbl1 FOR VALUES FROM (1) TO (maxvalue)
+ CREATE TABLE tbl2(f1 int REFERENCES tbl1 DEFERRABLE INITIALLY DEFERRED);
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+INSERT INTO fkpart10.tbl2 VALUES (0), (1);
+BEGIN;
+DELETE FROM fkpart10.tbl1 WHERE f1 = 0;
+UPDATE fkpart10.tbl1 SET f1 = 2 WHERE f1 = 1;
+INSERT INTO fkpart10.tbl1 VALUES (0), (1);
+COMMIT;
+DROP SCHEMA fkpart10 CASCADE;