Fix error code for referential action RESTRICT
authorPeter Eisentraut <peter@eisentraut.org>
Mon, 2 Dec 2024 07:18:36 +0000 (08:18 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Mon, 2 Dec 2024 07:22:34 +0000 (08:22 +0100)
According to the SQL standard, if the referential action RESTRICT is
triggered, it has its own error code.  We previously didn't use that,
we just used the error code for foreign key violation.  But RESTRICT
is not necessarily an actual foreign key violation.  The foreign key
might still be satisfied in theory afterwards, but the RESTRICT
setting prevents the action even then.  So it's a separate kind of
error condition.

Discussion: https://www.postgresql.org/message-id/ea5b2777-266a-46fa-852f-6fca6ec480ad@eisentraut.org

src/backend/utils/adt/ri_triggers.c
src/test/regress/expected/collate.icu.utf8.out
src/test/regress/expected/foreign_key.out
src/test/regress/expected/without_overlaps.out

index 91792cb2a47b778b7bdb2ded1b144d19c6495ddc..3185f48afa6b86942cf3e639710d4f43976f4216 100644 (file)
@@ -228,6 +228,7 @@ static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo,
                                                        RI_QueryKey *qkey, SPIPlanPtr qplan,
                                                        Relation fk_rel, Relation pk_rel,
                                                        TupleTableSlot *oldslot, TupleTableSlot *newslot,
+                                                       bool is_restrict,
                                                        bool detectNewRows, int expect_OK);
 static void ri_ExtractValues(Relation rel, TupleTableSlot *slot,
                                                         const RI_ConstraintInfo *riinfo, bool rel_is_pk,
@@ -235,7 +236,7 @@ static void ri_ExtractValues(Relation rel, TupleTableSlot *slot,
 static void ri_ReportViolation(const RI_ConstraintInfo *riinfo,
                                                           Relation pk_rel, Relation fk_rel,
                                                           TupleTableSlot *violatorslot, TupleDesc tupdesc,
-                                                          int queryno, bool partgone) pg_attribute_noreturn();
+                                                          int queryno, bool is_restrict, bool partgone) pg_attribute_noreturn();
 
 
 /*
@@ -449,6 +450,7 @@ RI_FKey_check(TriggerData *trigdata)
        ri_PerformCheck(riinfo, &qkey, qplan,
                                        fk_rel, pk_rel,
                                        NULL, newslot,
+                                       false,
                                        pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE,
                                        SPI_OK_SELECT);
 
@@ -613,6 +615,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
        result = ri_PerformCheck(riinfo, &qkey, qplan,
                                                         fk_rel, pk_rel,
                                                         oldslot, NULL,
+                                                        false,
                                                         true,  /* treat like update */
                                                         SPI_OK_SELECT);
 
@@ -800,6 +803,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
        ri_PerformCheck(riinfo, &qkey, qplan,
                                        fk_rel, pk_rel,
                                        oldslot, NULL,
+                                       !is_no_action,
                                        true,           /* must detect new rows */
                                        SPI_OK_SELECT);
 
@@ -901,6 +905,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
        ri_PerformCheck(riinfo, &qkey, qplan,
                                        fk_rel, pk_rel,
                                        oldslot, NULL,
+                                       false,
                                        true,           /* must detect new rows */
                                        SPI_OK_DELETE);
 
@@ -1017,6 +1022,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
        ri_PerformCheck(riinfo, &qkey, qplan,
                                        fk_rel, pk_rel,
                                        oldslot, newslot,
+                                       false,
                                        true,           /* must detect new rows */
                                        SPI_OK_UPDATE);
 
@@ -1244,6 +1250,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
        ri_PerformCheck(riinfo, &qkey, qplan,
                                        fk_rel, pk_rel,
                                        oldslot, NULL,
+                                       false,
                                        true,           /* must detect new rows */
                                        SPI_OK_UPDATE);
 
@@ -1690,7 +1697,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
                ri_ReportViolation(&fake_riinfo,
                                                   pk_rel, fk_rel,
                                                   slot, tupdesc,
-                                                  RI_PLAN_CHECK_LOOKUPPK, false);
+                                                  RI_PLAN_CHECK_LOOKUPPK, false, false);
 
                ExecDropSingleTupleTableSlot(slot);
        }
@@ -1906,7 +1913,7 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
                        fake_riinfo.pk_attnums[i] = i + 1;
 
                ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel,
-                                                  slot, tupdesc, 0, true);
+                                                  slot, tupdesc, 0, false, true);
        }
 
        if (SPI_finish() != SPI_OK_FINISH)
@@ -2387,6 +2394,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
                                RI_QueryKey *qkey, SPIPlanPtr qplan,
                                Relation fk_rel, Relation pk_rel,
                                TupleTableSlot *oldslot, TupleTableSlot *newslot,
+                               bool is_restrict,
                                bool detectNewRows, int expect_OK)
 {
        Relation        query_rel,
@@ -2511,7 +2519,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
                                                   pk_rel, fk_rel,
                                                   newslot ? newslot : oldslot,
                                                   NULL,
-                                                  qkey->constr_queryno, false);
+                                                  qkey->constr_queryno, is_restrict, false);
 
        return SPI_processed != 0;
 }
@@ -2552,7 +2560,7 @@ static void
 ri_ReportViolation(const RI_ConstraintInfo *riinfo,
                                   Relation pk_rel, Relation fk_rel,
                                   TupleTableSlot *violatorslot, TupleDesc tupdesc,
-                                  int queryno, bool partgone)
+                                  int queryno, bool is_restrict, bool partgone)
 {
        StringInfoData key_names;
        StringInfoData key_values;
@@ -2682,6 +2690,20 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
                                 errdetail("Key is not present in table \"%s\".",
                                                   RelationGetRelationName(pk_rel)),
                                 errtableconstraint(fk_rel, NameStr(riinfo->conname))));
+       else if (is_restrict)
+               ereport(ERROR,
+                               (errcode(ERRCODE_RESTRICT_VIOLATION),
+                                errmsg("update or delete on table \"%s\" violates RESTRICT setting of foreign key constraint \"%s\" on table \"%s\"",
+                                               RelationGetRelationName(pk_rel),
+                                               NameStr(riinfo->conname),
+                                               RelationGetRelationName(fk_rel)),
+                                has_perm ?
+                                errdetail("Key (%s)=(%s) is referenced from table \"%s\".",
+                                                  key_names.data, key_values.data,
+                                                  RelationGetRelationName(fk_rel)) :
+                                errdetail("Key is referenced from table \"%s\".",
+                                                  RelationGetRelationName(fk_rel)),
+                                errtableconstraint(fk_rel, NameStr(riinfo->conname))));
        else
                ereport(ERROR,
                                (errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
index 9f5e57428e82148ecd401b2a89379009e6393f64..6cbadafcfbf6581f5fe540f389b91785992174ee 100644 (file)
@@ -2051,8 +2051,8 @@ CREATE TABLE test12fk (a int, b text COLLATE case_insensitive REFERENCES test12p
 INSERT INTO test12pk VALUES ('abc');
 INSERT INTO test12fk VALUES (1, 'abc'), (2, 'ABC');
 UPDATE test12pk SET x = 'ABC' WHERE x = 'abc';  -- restrict violation
-ERROR:  update or delete on table "test12pk" violates foreign key constraint "test12fk_b_fkey" on table "test12fk"
-DETAIL:  Key (x)=(abc) is still referenced from table "test12fk".
+ERROR:  update or delete on table "test12pk" violates RESTRICT setting of foreign key constraint "test12fk_b_fkey" on table "test12fk"
+DETAIL:  Key (x)=(abc) is referenced from table "test12fk".
 SELECT * FROM test12pk;
   x  
 -----
index a5165270c2d45c86f34a7ee0bd0a4b0b412320a9..3f459f70ac1e63a2a0d4552a7801fc193908589a 100644 (file)
@@ -1439,11 +1439,11 @@ insert into pp values(11);
 update pp set f1=f1+1;
 insert into cc values(13);
 update pp set f1=f1+1; -- fail
-ERROR:  update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
-DETAIL:  Key (f1)=(13) is still referenced from table "cc".
+ERROR:  update or delete on table "pp" violates RESTRICT setting of foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL:  Key (f1)=(13) is referenced from table "cc".
 delete from pp where f1 = 13; -- fail
-ERROR:  update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
-DETAIL:  Key (f1)=(13) is still referenced from table "cc".
+ERROR:  update or delete on table "pp" violates RESTRICT setting of foreign key constraint "cc_f1_fkey" on table "cc"
+DETAIL:  Key (f1)=(13) is referenced from table "cc".
 drop table pp, cc;
 --
 -- Test interaction of foreign-key optimization with rules (bug #14219)
@@ -2664,11 +2664,11 @@ ALTER TABLE fk ADD FOREIGN KEY (a) REFERENCES pk ON UPDATE RESTRICT ON DELETE RE
 CREATE TABLE fk_d PARTITION OF fk DEFAULT;
 INSERT INTO fk VALUES (20), (30);
 DELETE FROM pk WHERE a = 20;
-ERROR:  update or delete on table "pk11" violates foreign key constraint "fk_a_fkey2" on table "fk"
-DETAIL:  Key (a)=(20) is still referenced from table "fk".
+ERROR:  update or delete on table "pk11" violates RESTRICT setting of foreign key constraint "fk_a_fkey2" on table "fk"
+DETAIL:  Key (a)=(20) is referenced from table "fk".
 UPDATE pk SET a = 90 WHERE a = 30;
-ERROR:  update or delete on table "pk" violates foreign key constraint "fk_a_fkey" on table "fk"
-DETAIL:  Key (a)=(30) is still referenced from table "fk".
+ERROR:  update or delete on table "pk" violates RESTRICT setting of foreign key constraint "fk_a_fkey" on table "fk"
+DETAIL:  Key (a)=(30) is referenced from table "fk".
 SELECT tableoid::regclass, * FROM fk;
  tableoid | a  
 ----------+----
index 8b5ecab6fd84cc57eaf2e0a6016b032bdaa8d903..475a56fcfdb5ad73bf9b8bcbbdfcd14246591c28 100644 (file)
@@ -1759,8 +1759,8 @@ UPDATE temporal_rng
 SET valid_at = CASE WHEN lower(valid_at) = '2018-01-01' THEN daterange('2018-01-01', '2018-01-05')
                     WHEN lower(valid_at) = '2018-02-01' THEN daterange('2018-01-05', '2018-03-01') END
 WHERE id = '[6,7)';
-ERROR:  update or delete on table "temporal_rng" violates foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
-DETAIL:  Key (id, valid_at)=([6,7), [2018-01-01,2018-02-01)) is still referenced from table "temporal_fk_rng2rng".
+ERROR:  update or delete on table "temporal_rng" violates RESTRICT setting of foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
+DETAIL:  Key (id, valid_at)=([6,7), [2018-01-01,2018-02-01)) is referenced from table "temporal_fk_rng2rng".
 -- a PK update that fails because both are referenced (even before commit):
 BEGIN;
   ALTER TABLE temporal_fk_rng2rng
@@ -1768,14 +1768,14 @@ BEGIN;
     DEFERRABLE INITIALLY DEFERRED;
   UPDATE temporal_rng SET valid_at = daterange('2016-01-01', '2016-02-01')
   WHERE id = '[5,6)' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "temporal_rng" violates foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
-DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is still referenced from table "temporal_fk_rng2rng".
+ERROR:  update or delete on table "temporal_rng" violates RESTRICT setting of foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
+DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is referenced from table "temporal_fk_rng2rng".
 ROLLBACK;
 -- changing the scalar part fails:
 UPDATE temporal_rng SET id = '[7,8)'
 WHERE id = '[5,6)' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "temporal_rng" violates foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
-DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is still referenced from table "temporal_fk_rng2rng".
+ERROR:  update or delete on table "temporal_rng" violates RESTRICT setting of foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
+DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is referenced from table "temporal_fk_rng2rng".
 -- then delete the objecting FK record and the same PK update succeeds:
 DELETE FROM temporal_fk_rng2rng WHERE id = '[3,4)';
 UPDATE temporal_rng SET valid_at = daterange('2016-01-01', '2016-02-01')
@@ -1840,8 +1840,8 @@ BEGIN;
     ALTER CONSTRAINT temporal_fk_rng2rng_fk
     DEFERRABLE INITIALLY DEFERRED;
   DELETE FROM temporal_rng WHERE id = '[5,6)' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "temporal_rng" violates foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
-DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is still referenced from table "temporal_fk_rng2rng".
+ERROR:  update or delete on table "temporal_rng" violates RESTRICT setting of foreign key constraint "temporal_fk_rng2rng_fk" on table "temporal_fk_rng2rng"
+DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is referenced from table "temporal_fk_rng2rng".
 ROLLBACK;
 -- then delete the objecting FK record and the same PK delete succeeds:
 DELETE FROM temporal_fk_rng2rng WHERE id = '[3,4)';
@@ -2243,8 +2243,8 @@ UPDATE temporal_mltrng
 SET valid_at = CASE WHEN lower(valid_at) = '2018-01-01' THEN datemultirange(daterange('2018-01-01', '2018-01-05'))
                     WHEN lower(valid_at) = '2018-02-01' THEN datemultirange(daterange('2018-01-05', '2018-03-01')) END
 WHERE id = '[6,7)';
-ERROR:  update or delete on table "temporal_mltrng" violates foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
-DETAIL:  Key (id, valid_at)=([6,7), {[2018-01-01,2018-02-01)}) is still referenced from table "temporal_fk_mltrng2mltrng".
+ERROR:  update or delete on table "temporal_mltrng" violates RESTRICT setting of foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
+DETAIL:  Key (id, valid_at)=([6,7), {[2018-01-01,2018-02-01)}) is referenced from table "temporal_fk_mltrng2mltrng".
 -- a PK update that fails because both are referenced (even before commit):
 BEGIN;
   ALTER TABLE temporal_fk_mltrng2mltrng
@@ -2252,14 +2252,14 @@ BEGIN;
     DEFERRABLE INITIALLY DEFERRED;
   UPDATE temporal_mltrng SET valid_at = datemultirange(daterange('2016-01-01', '2016-02-01'))
   WHERE id = '[5,6)' AND valid_at = datemultirange(daterange('2018-01-01', '2018-02-01'));
-ERROR:  update or delete on table "temporal_mltrng" violates foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
-DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is still referenced from table "temporal_fk_mltrng2mltrng".
+ERROR:  update or delete on table "temporal_mltrng" violates RESTRICT setting of foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
+DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is referenced from table "temporal_fk_mltrng2mltrng".
 ROLLBACK;
 -- changing the scalar part fails:
 UPDATE temporal_mltrng SET id = '[7,8)'
 WHERE id = '[5,6)' AND valid_at = datemultirange(daterange('2018-01-01', '2018-02-01'));
-ERROR:  update or delete on table "temporal_mltrng" violates foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
-DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is still referenced from table "temporal_fk_mltrng2mltrng".
+ERROR:  update or delete on table "temporal_mltrng" violates RESTRICT setting of foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
+DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is referenced from table "temporal_fk_mltrng2mltrng".
 --
 -- test FK referenced deletes NO ACTION
 --
@@ -2317,8 +2317,8 @@ BEGIN;
     ALTER CONSTRAINT temporal_fk_mltrng2mltrng_fk
     DEFERRABLE INITIALLY DEFERRED;
   DELETE FROM temporal_mltrng WHERE id = '[5,6)' AND valid_at = datemultirange(daterange('2018-01-01', '2018-02-01'));
-ERROR:  update or delete on table "temporal_mltrng" violates foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
-DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is still referenced from table "temporal_fk_mltrng2mltrng".
+ERROR:  update or delete on table "temporal_mltrng" violates RESTRICT setting of foreign key constraint "temporal_fk_mltrng2mltrng_fk" on table "temporal_fk_mltrng2mltrng"
+DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is referenced from table "temporal_fk_mltrng2mltrng".
 ROLLBACK;
 --
 -- FK between partitioned tables: ranges
@@ -2432,8 +2432,8 @@ INSERT INTO temporal_partitioned_fk_rng2rng (id, valid_at, parent_id) VALUES ('[
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,6)' AND valid_at = daterange('2018-02-01', '2018-03-01');
 -- should fail:
 DELETE FROM temporal_partitioned_rng WHERE id = '[5,6)' AND valid_at = daterange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_valid_at_fkey" on table "temporal_partitioned_fk_rng2rng"
-DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is still referenced from table "temporal_partitioned_fk_rng2rng".
+ERROR:  update or delete on table "tp1" violates RESTRICT setting of foreign key constraint "temporal_partitioned_fk_rng2rng_parent_id_valid_at_fkey" on table "temporal_partitioned_fk_rng2rng"
+DETAIL:  Key (id, valid_at)=([5,6), [2018-01-01,2018-02-01)) is referenced from table "temporal_partitioned_fk_rng2rng".
 --
 -- partitioned FK referenced updates CASCADE
 --
@@ -2588,8 +2588,8 @@ INSERT INTO temporal_partitioned_fk_mltrng2mltrng (id, valid_at, parent_id) VALU
 DELETE FROM temporal_partitioned_mltrng WHERE id = '[5,6)' AND valid_at = datemultirange(daterange('2018-02-01', '2018-03-01'));
 -- should fail:
 DELETE FROM temporal_partitioned_mltrng WHERE id = '[5,6)' AND valid_at = datemultirange(daterange('2018-01-01', '2018-02-01'));
-ERROR:  update or delete on table "tp1" violates foreign key constraint "temporal_partitioned_fk_mltrng2mltrng_parent_id_valid_at_fkey1" on table "temporal_partitioned_fk_mltrng2mltrng"
-DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is still referenced from table "temporal_partitioned_fk_mltrng2mltrng".
+ERROR:  update or delete on table "tp1" violates RESTRICT setting of foreign key constraint "temporal_partitioned_fk_mltrng2mltrng_parent_id_valid_at_fkey1" on table "temporal_partitioned_fk_mltrng2mltrng"
+DETAIL:  Key (id, valid_at)=([5,6), {[2018-01-01,2018-02-01)}) is referenced from table "temporal_partitioned_fk_mltrng2mltrng".
 --
 -- partitioned FK referenced updates CASCADE
 --