From e8c9fd5fdf768323911f7088e8287f63b513c3c6 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 18 Jun 2012 19:37:23 -0400 Subject: [PATCH] Allow ON UPDATE/DELETE SET DEFAULT plans to be cached. Once upon a time, somebody was worried that cached RI plans wouldn't get remade with new default values after ALTER TABLE ... SET DEFAULT, so they didn't allow caching of plans for ON UPDATE/DELETE SET DEFAULT actions. That time is long gone, though (and even at the time I doubt this was the greatest hazard posed by ALTER TABLE...). So allow these triggers to cache their plans just like the others. The cache_plan argument to ri_PlanCheck is now vestigial, since there are no callers that don't pass "true"; but I left it alone in case there is any future need for it. --- src/backend/utils/adt/ri_triggers.c | 24 +++++++-------- src/test/regress/expected/foreign_key.out | 36 +++++++++++++++++++++++ src/test/regress/sql/foreign_key.sql | 17 +++++++++++ 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index cd1bb262a3..5b439aab6f 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2155,12 +2155,12 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) elog(ERROR, "SPI_connect failed"); /* - * Prepare a plan for the set default delete operation. - * Unfortunately we need to do it on every invocation because the - * default value could potentially change between calls. + * Fetch or prepare a saved plan for the set default delete + * operation */ ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_DEL_DOUPDATE); + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; @@ -2207,9 +2207,9 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) } appendStringInfoString(&querybuf, qualbuf.data); - /* Prepare the plan, don't save it */ + /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, - &qkey, fk_rel, pk_rel, false); + &qkey, fk_rel, pk_rel, true); } /* @@ -2239,7 +2239,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); /* - * Handle MATCH PARTIAL set null delete. + * Handle MATCH PARTIAL set default delete. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, @@ -2348,12 +2348,12 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) elog(ERROR, "SPI_connect failed"); /* - * Prepare a plan for the set default delete operation. - * Unfortunately we need to do it on every invocation because the - * default value could potentially change between calls. + * Fetch or prepare a saved plan for the set default update + * operation */ ri_BuildQueryKey(&qkey, &riinfo, RI_PLAN_SETDEFAULT_UPD_DOUPDATE); + if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL) { StringInfoData querybuf; StringInfoData qualbuf; @@ -2400,9 +2400,9 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) } appendStringInfoString(&querybuf, qualbuf.data); - /* Prepare the plan, don't save it */ + /* Prepare and save the plan */ qplan = ri_PlanCheck(querybuf.data, riinfo.nkeys, queryoids, - &qkey, fk_rel, pk_rel, false); + &qkey, fk_rel, pk_rel, true); } /* @@ -2432,7 +2432,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS) return PointerGetDatum(NULL); /* - * Handle MATCH PARTIAL set null delete. + * Handle MATCH PARTIAL set default update. */ case FKCONSTR_MATCH_PARTIAL: ereport(ERROR, diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 305dfe20d4..a63a89f40b 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -1319,3 +1319,39 @@ begin; (2 rows) commit; +-- +-- Test that SET DEFAULT actions recognize updates to default values +-- +create temp table defp (f1 int primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "defp_pkey" for table "defp" +create temp table defc (f1 int default 0 + references defp on delete set default); +insert into defp values (0), (1), (2); +insert into defc values (2); +select * from defc; + f1 +---- + 2 +(1 row) + +delete from defp where f1 = 2; +select * from defc; + f1 +---- + 0 +(1 row) + +delete from defp where f1 = 0; -- fail +ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc" +DETAIL: Key (f1)=(0) is still referenced from table "defc". +alter table defc alter column f1 set default 1; +delete from defp where f1 = 0; +select * from defc; + f1 +---- + 1 +(1 row) + +delete from defp where f1 = 1; -- fail +ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc" +DETAIL: Key (f1)=(1) is still referenced from table "defc". diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 874d9f7a33..43703d234e 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -943,3 +943,20 @@ begin; update selfref set a = 456 where a = 123; select a, b from selfref; commit; + +-- +-- Test that SET DEFAULT actions recognize updates to default values +-- +create temp table defp (f1 int primary key); +create temp table defc (f1 int default 0 + references defp on delete set default); +insert into defp values (0), (1), (2); +insert into defc values (2); +select * from defc; +delete from defp where f1 = 2; +select * from defc; +delete from defp where f1 = 0; -- fail +alter table defc alter column f1 set default 1; +delete from defp where f1 = 0; +select * from defc; +delete from defp where f1 = 1; -- fail