On 24/09/26 00:42, Amit Langote wrote:
Hi,It was pointed out to me offlist by Nikolay Samokhvalov that the RI fast path (ri_FastPathCheck) doesn't check EXECUTE privilege on the functions it invokes on the FK values. There are two such functions: the equality operator's function, which goes into the ScanKey the index AM evaluates, and the implicit cast function, which is used when the FK value must be converted to the PK column's type. In the SPI path's case, because the operator appears in the generated query's WHERE clause and the cast as $1::pktype, ExecutorStart checked EXECUTE on both as part of initializing the query, as the referenced table's owner. So while I added ri_CheckPermissions() to parallel the executor's ExecCheckPermissions(), I missed the non-relation permission checks that occur as part of expression initialization. The attached patch adds ri_CheckFunctionPermissions(), which does object_aclcheck(ProcedureRelationId, ..., ACL_EXECUTE) plus InvokeFunctionExecuteHook() for each of the two functions, and calls it once the fast-path metadata is populated. That puts the checks at the same point as the existing relation permission check. Regression tests are included for both functions. They reach the fast path through per-row validation of a new constraint, and repeat each case against a partitioned referenced table, which takes the SPI path, to establish that the two behave identically. I'll add an open item. I would like to get this committed by EOW if there are no comments.
I take a look on the patch, it looks good to me. Just a minor comment: ri_CheckFunctionPermissions(riinfo, fpmeta) passes both when fpmeta == riinfo->fpmeta. I'm wondering if we could just pass riinfo? IIUC this patch only fix the case for FastPath without batching right? Since batching is still on master, I'm wondering if we could also fix it. See attached patch (v2-0001 is your v1-0001). -- Matheus Alcantara EDB: https://www.enterprisedb.com
From 7aea3239e37d0588413458f1a97603069ee1c960 Mon Sep 17 00:00:00 2001 From: Amit Langote <[email protected]> Date: Thu, 24 Sep 2026 09:07:48 +0900 Subject: [PATCH v2 1/2] Check EXECUTE privilege on functions invoked by the RI fast path ri_FastPathCheck() invokes two functions on the FK values: the equality operator's function, via the ScanKey the index AM evaluates, and, when the FK column's type differs from the PK column's, the implicit cast function. The SPI path had EXECUTE on both checked by ExecInitFunc() while initializing the generated query, as the referenced table's owner, but the fast path skipped both checks. Add ri_CheckFunctionPermissions() and call it once the fast-path metadata is populated, under the switch to the referenced table's owner, so the checks happen at the same point as the existing relation permission check. Add a regression test for each function, reaching the fast path through per-row validation of a new constraint. Reported-by: Nikolay Samokhvalov <[email protected]> Discussion: https://postgr.es/m/ Backpatch-through: 19 --- src/backend/utils/adt/ri_triggers.c | 39 +++++++++++ src/test/regress/expected/foreign_key.out | 81 +++++++++++++++++++++++ src/test/regress/sql/foreign_key.sql | 81 +++++++++++++++++++++++ 3 files changed, 201 insertions(+) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 40c1591ac7b..e584df17980 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -32,11 +32,13 @@ #include "access/tableam.h" #include "access/xact.h" #include "catalog/index.h" +#include "catalog/objectaccess.h" #include "catalog/pg_am_d.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_index.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_proc.h" #include "commands/trigger.h" #include "executor/executor.h" #include "executor/spi.h" @@ -399,6 +401,8 @@ static void ri_CheckPermissions(const RI_ConstraintInfo *riinfo, Relation query_rel); static bool recheck_matched_pk_tuple(Relation idxrel, ScanKeyData *skeys, int nkeys, TupleTableSlot *new_slot); +static void ri_CheckFunctionPermissions(const RI_ConstraintInfo *riinfo, + const FastPathMeta *fpmeta); static void build_index_scankeys(const RI_ConstraintInfo *riinfo, FastPathMeta *fpmeta, Relation idx_rel, Datum *pk_vals, @@ -2971,6 +2975,7 @@ ri_FastPathCheck(RI_ConstraintInfo *riinfo, ri_populate_fastpath_metadata(riinfo, fk_rel, idx_rel); } Assert(riinfo->fpmeta); + ri_CheckFunctionPermissions(riinfo, riinfo->fpmeta); ri_ExtractValues(fk_rel, newslot, riinfo, false, pk_vals, pk_nulls); build_index_scankeys(riinfo, riinfo->fpmeta, idx_rel, pk_vals, pk_nulls, skey); @@ -3715,6 +3720,40 @@ recheck_matched_pk_tuple(Relation idxrel, ScanKeyData *skeys, int nkeys, return matched; } +/* + * ri_CheckFunctionPermissions + * Check EXECUTE privilege on the functions the fast path invokes on the + * FK values, as the referenced table's owner. + * + * The SPI path had these checks done by ExecInitFunc() when initializing + * the generated query: the equality operator's function appears in its WHERE + * clause and the cast function, if any, in the cast applied to the parameter. + * Call with the user id already switched to the referenced table's owner. + */ +static void +ri_CheckFunctionPermissions(const RI_ConstraintInfo *riinfo, + const FastPathMeta *fpmeta) +{ + for (int i = 0; i < riinfo->nkeys; i++) + { + Oid funcs[2] = {fpmeta->regops[i], fpmeta->cast_func_finfo[i].fn_oid}; + + for (int j = 0; j < lengthof(funcs); j++) + { + AclResult aclresult; + + if (!OidIsValid(funcs[j])) + continue; + aclresult = object_aclcheck(ProcedureRelationId, funcs[j], + GetUserId(), ACL_EXECUTE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_FUNCTION, + get_func_name(funcs[j])); + InvokeFunctionExecuteHook(funcs[j]); + } + } +} + /* * build_index_scankeys * Build ScanKeys for a direct index probe of the PK's unique index. diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index 40be9c4f75d..f5b10c73c10 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -3803,6 +3803,87 @@ NOTICE: fp_auto_pk called NOTICE: fp_auto_pk called DROP TABLE fp_fk_cci, fp_pk_cci; DROP FUNCTION fp_auto_pk; +-- The fast path must check EXECUTE, as the referenced table's owner, on the +-- functions it invokes on the FK values: the equality operator's function +-- and, for a cross-type FK, the implicit cast function. Validation of a new +-- constraint by a role that cannot use the bulk check (RLS is enabled on the +-- referenced table) runs per-row checks through the fast path. +-- +-- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere +-- with nearby tests. +BEGIN; +CREATE ROLE regress_ri_pkowner; +CREATE ROLE regress_ri_fkowner; +-- A private btree opclass whose equality function we can revoke without +-- affecting anything else. +CREATE FUNCTION regress_ri_int4eq(int4, int4) RETURNS bool + AS 'int4eq' LANGUAGE internal IMMUTABLE STRICT; +CREATE OPERATOR === (LEFTARG = int4, RIGHTARG = int4, + FUNCTION = regress_ri_int4eq, COMMUTATOR = ===, + RESTRICT = eqsel, JOIN = eqjoinsel); +CREATE OPERATOR CLASS regress_ri_int4_ops FOR TYPE int4 USING btree AS + OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 ===, OPERATOR 4 >=, OPERATOR 5 >, + FUNCTION 1 btint4cmp(int4, int4); +REVOKE EXECUTE ON FUNCTION regress_ri_int4eq(int4, int4) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION regress_ri_int4eq(int4, int4) TO regress_ri_fkowner; +-- An FK column type needing a user-defined implicit cast to the PK type. +CREATE TYPE fkenum AS ENUM ('one'); +CREATE FUNCTION fkenum_to_int4(fkenum) RETURNS int4 + AS 'SELECT 1' LANGUAGE sql IMMUTABLE STRICT; +CREATE CAST (fkenum AS int4) WITH FUNCTION fkenum_to_int4(fkenum) AS IMPLICIT; +REVOKE EXECUTE ON FUNCTION fkenum_to_int4(fkenum) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION fkenum_to_int4(fkenum) TO regress_ri_fkowner; +CREATE TABLE pktable_acl_op (a int4); +CREATE UNIQUE INDEX ON pktable_acl_op (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_cast (a int4 PRIMARY KEY); +CREATE TABLE pktable_acl_op_part (a int4) PARTITION BY LIST (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_op_part1 PARTITION OF pktable_acl_op_part DEFAULT; +CREATE UNIQUE INDEX ON pktable_acl_op_part (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_cast_part (a int4 PRIMARY KEY) PARTITION BY LIST (a); +INSERT INTO pktable_acl_op VALUES (1); +INSERT INTO pktable_acl_cast VALUES (1); +ALTER TABLE pktable_acl_op OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_cast OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_op_part OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_cast_part OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_op ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_cast ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_op_part ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_cast_part ENABLE ROW LEVEL SECURITY; +GRANT REFERENCES ON pktable_acl_op, pktable_acl_cast, pktable_acl_op_part, pktable_acl_cast_part TO regress_ri_fkowner; +CREATE TABLE fktable_acl_op (a int4); +CREATE TABLE fktable_acl_cast (a fkenum); +INSERT INTO fktable_acl_op VALUES (1); +INSERT INTO fktable_acl_cast VALUES ('one'); +ALTER TABLE fktable_acl_op OWNER TO regress_ri_fkowner; +ALTER TABLE fktable_acl_cast OWNER TO regress_ri_fkowner; +-- The FK owner holds EXECUTE on both functions; the PK owner does not, and +-- it is the PK owner whose privileges apply. +SET ROLE regress_ri_fkowner; +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a); -- fails +ERROR: permission denied for function regress_ri_int4eq +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a); -- fails +ERROR: permission denied for function fkenum_to_int4 +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op_part (a); -- fails (SPI path message) +ERROR: permission denied for function regress_ri_int4eq +CONTEXT: SQL statement "SELECT 1 FROM "public"."pktable_acl_op_part" x WHERE "a" OPERATOR(public.===) $1 FOR KEY SHARE OF x" +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast_part (a); -- fails (SPI path message) +ERROR: permission denied for function fkenum_to_int4 +CONTEXT: SQL statement "SELECT 1 FROM "public"."pktable_acl_cast_part" x WHERE "a" OPERATOR(pg_catalog.=) $1::pg_catalog.int4 FOR KEY SHARE OF x" +ROLLBACK TO SAVEPOINT s; +RESET ROLE; +ROLLBACK; -- A STABLE cast used by an FK check must see changes made by earlier AFTER -- triggers, using the check's snapshot rather than the outer query's snapshot. -- Compare the per-row fast path with a partitioned-parent SPI check. diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index 89405dff99e..bd5a47a836c 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -2768,6 +2768,87 @@ INSERT INTO fp_fk_cci VALUES (1), (2), (3); DROP TABLE fp_fk_cci, fp_pk_cci; DROP FUNCTION fp_auto_pk; +-- The fast path must check EXECUTE, as the referenced table's owner, on the +-- functions it invokes on the FK values: the equality operator's function +-- and, for a cross-type FK, the implicit cast function. Validation of a new +-- constraint by a role that cannot use the bulk check (RLS is enabled on the +-- referenced table) runs per-row checks through the fast path. +-- +-- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere +-- with nearby tests. +BEGIN; +CREATE ROLE regress_ri_pkowner; +CREATE ROLE regress_ri_fkowner; + +-- A private btree opclass whose equality function we can revoke without +-- affecting anything else. +CREATE FUNCTION regress_ri_int4eq(int4, int4) RETURNS bool + AS 'int4eq' LANGUAGE internal IMMUTABLE STRICT; +CREATE OPERATOR === (LEFTARG = int4, RIGHTARG = int4, + FUNCTION = regress_ri_int4eq, COMMUTATOR = ===, + RESTRICT = eqsel, JOIN = eqjoinsel); +CREATE OPERATOR CLASS regress_ri_int4_ops FOR TYPE int4 USING btree AS + OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 ===, OPERATOR 4 >=, OPERATOR 5 >, + FUNCTION 1 btint4cmp(int4, int4); +REVOKE EXECUTE ON FUNCTION regress_ri_int4eq(int4, int4) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION regress_ri_int4eq(int4, int4) TO regress_ri_fkowner; + +-- An FK column type needing a user-defined implicit cast to the PK type. +CREATE TYPE fkenum AS ENUM ('one'); +CREATE FUNCTION fkenum_to_int4(fkenum) RETURNS int4 + AS 'SELECT 1' LANGUAGE sql IMMUTABLE STRICT; +CREATE CAST (fkenum AS int4) WITH FUNCTION fkenum_to_int4(fkenum) AS IMPLICIT; +REVOKE EXECUTE ON FUNCTION fkenum_to_int4(fkenum) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION fkenum_to_int4(fkenum) TO regress_ri_fkowner; + +CREATE TABLE pktable_acl_op (a int4); +CREATE UNIQUE INDEX ON pktable_acl_op (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_cast (a int4 PRIMARY KEY); +CREATE TABLE pktable_acl_op_part (a int4) PARTITION BY LIST (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_op_part1 PARTITION OF pktable_acl_op_part DEFAULT; +CREATE UNIQUE INDEX ON pktable_acl_op_part (a regress_ri_int4_ops); +CREATE TABLE pktable_acl_cast_part (a int4 PRIMARY KEY) PARTITION BY LIST (a); +INSERT INTO pktable_acl_op VALUES (1); +INSERT INTO pktable_acl_cast VALUES (1); +ALTER TABLE pktable_acl_op OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_cast OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_op_part OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_cast_part OWNER TO regress_ri_pkowner; +ALTER TABLE pktable_acl_op ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_cast ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_op_part ENABLE ROW LEVEL SECURITY; +ALTER TABLE pktable_acl_cast_part ENABLE ROW LEVEL SECURITY; +GRANT REFERENCES ON pktable_acl_op, pktable_acl_cast, pktable_acl_op_part, pktable_acl_cast_part TO regress_ri_fkowner; + +CREATE TABLE fktable_acl_op (a int4); +CREATE TABLE fktable_acl_cast (a fkenum); +INSERT INTO fktable_acl_op VALUES (1); +INSERT INTO fktable_acl_cast VALUES ('one'); +ALTER TABLE fktable_acl_op OWNER TO regress_ri_fkowner; +ALTER TABLE fktable_acl_cast OWNER TO regress_ri_fkowner; + +-- The FK owner holds EXECUTE on both functions; the PK owner does not, and +-- it is the PK owner whose privileges apply. +SET ROLE regress_ri_fkowner; +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a); -- fails +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a); -- fails +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op_part (a); -- fails (SPI path message) +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast_part (a); -- fails (SPI path message) +ROLLBACK TO SAVEPOINT s; +RESET ROLE; +ROLLBACK; + -- A STABLE cast used by an FK check must see changes made by earlier AFTER -- triggers, using the check's snapshot rather than the outer query's snapshot. -- Compare the per-row fast path with a partitioned-parent SPI check. -- 2.50.1 (Apple Git-155)
From 7eed40b73cb564bd3575e0a40ee17ed795b77cef Mon Sep 17 00:00:00 2001 From: Matheus Alcantara <[email protected]> Date: Thu, 24 Sep 2026 11:33:01 -0300 Subject: [PATCH v2 2/2] Check EXECUTE on functions invoked by the batched RI fast path The RI fast-path EXECUTE-privilege check added ri_CheckFunctionPermissions() to verify ACL_EXECUTE on the two functions the fast path runs on the FK values but wired it only into the per-row path, ri_FastPathCheck(). The batched path used by ordinary DML, ri_FastPathBatchFlush(), invokes the same two functions through build_index_scankeys() and the SK_SEARCHARRAY array probe, so a plain INSERT, UPDATE, or COPY still skipped the check that the SPI path performs during executor startup. The privilege of the referenced table's owner is what applies, so a role lacking EXECUTE on a user-defined equality operator or cast function could reach it via routine DML. Call ri_CheckFunctionPermissions() once per flush in ri_FastPathBatchFlush(), next to the existing ri_CheckPermissions() relation check and under the same switch to the referenced table's owner, so the batched path performs the same checks as the per-row and SPI paths. --- src/backend/utils/adt/ri_triggers.c | 1 + src/test/regress/expected/foreign_key.out | 20 +++++++++++++++++++- src/test/regress/sql/foreign_key.sql | 18 +++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index e584df17980..2ea4ac04342 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -3141,6 +3141,7 @@ ri_FastPathBatchFlush(RI_FastPathEntry *fpentry, Relation fk_rel, ri_populate_fastpath_metadata(riinfo, fk_rel, idx_rel); } Assert(riinfo->fpmeta); + ri_CheckFunctionPermissions(riinfo, riinfo->fpmeta); /* * Take our own reference to the metadata for the duration of the flush. diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index f5b10c73c10..6cd6becf13c 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -3807,7 +3807,8 @@ DROP FUNCTION fp_auto_pk; -- functions it invokes on the FK values: the equality operator's function -- and, for a cross-type FK, the implicit cast function. Validation of a new -- constraint by a role that cannot use the bulk check (RLS is enabled on the --- referenced table) runs per-row checks through the fast path. +-- referenced table) runs per-row checks through the fast path, and ordinary +-- DML runs the batched fast path; both are exercised below. -- -- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere -- with nearby tests. @@ -3882,6 +3883,23 @@ ALTER TABLE fktable_acl_cast ERROR: permission denied for function fkenum_to_int4 CONTEXT: SQL statement "SELECT 1 FROM "public"."pktable_acl_cast_part" x WHERE "a" OPERATOR(pg_catalog.=) $1::pg_catalog.int4 FOR KEY SHARE OF x" ROLLBACK TO SAVEPOINT s; +-- Ordinary DML uses the batched fast path, distinct from the per-row path that +-- validation exercises above. Create each constraint NOT VALID so its +-- functions are first reached when a later INSERT is checked; the EXECUTE +-- check, made as the PK owner, then fails before the probe even though the +-- inserted value satisfies the FK. +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a) NOT VALID; +INSERT INTO fktable_acl_op VALUES (1); -- fails +ERROR: permission denied for function regress_ri_int4eq +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a) NOT VALID; +INSERT INTO fktable_acl_cast VALUES ('one'); -- fails +ERROR: permission denied for function fkenum_to_int4 +ROLLBACK TO SAVEPOINT s; RESET ROLE; ROLLBACK; -- A STABLE cast used by an FK check must see changes made by earlier AFTER diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index bd5a47a836c..7b101bf2ba1 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -2772,7 +2772,8 @@ DROP FUNCTION fp_auto_pk; -- functions it invokes on the FK values: the equality operator's function -- and, for a cross-type FK, the implicit cast function. Validation of a new -- constraint by a role that cannot use the bulk check (RLS is enabled on the --- referenced table) runs per-row checks through the fast path. +-- referenced table) runs per-row checks through the fast path, and ordinary +-- DML runs the batched fast path; both are exercised below. -- -- Wrapped with BEGIN...ROLLBACK to ensure opclasses used here don't interfere -- with nearby tests. @@ -2846,6 +2847,21 @@ SAVEPOINT s; ALTER TABLE fktable_acl_cast ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast_part (a); -- fails (SPI path message) ROLLBACK TO SAVEPOINT s; +-- Ordinary DML uses the batched fast path, distinct from the per-row path that +-- validation exercises above. Create each constraint NOT VALID so its +-- functions are first reached when a later INSERT is checked; the EXECUTE +-- check, made as the PK owner, then fails before the probe even though the +-- inserted value satisfies the FK. +SAVEPOINT s; +ALTER TABLE fktable_acl_op + ADD FOREIGN KEY (a) REFERENCES pktable_acl_op (a) NOT VALID; +INSERT INTO fktable_acl_op VALUES (1); -- fails +ROLLBACK TO SAVEPOINT s; +SAVEPOINT s; +ALTER TABLE fktable_acl_cast + ADD FOREIGN KEY (a) REFERENCES pktable_acl_cast (a) NOT VALID; +INSERT INTO fktable_acl_cast VALUES ('one'); -- fails +ROLLBACK TO SAVEPOINT s; RESET ROLE; ROLLBACK; -- 2.50.1 (Apple Git-155)
