diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 521ec04f6b8..35067822082 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -31,6 +31,7 @@ #include "access/tableam.h" #include "access/xact.h" #include "catalog/index.h" +#include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am_d.h" #include "catalog/pg_collation.h" @@ -45,6 +46,7 @@ #include "miscadmin.h" #include "parser/parse_coerce.h" #include "parser/parse_relation.h" +#include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/datum.h" @@ -2789,6 +2791,14 @@ ri_FastPathCheck(RI_ConstraintInfo *riinfo, pk_rel = table_open(riinfo->pk_relid, RowShareLock); + /* + * The row lock taken below is refused in a read-only transaction unless + * the referenced table is temporary, as ExecCheckXactReadOnly() refuses + * the SPI path's SELECT ... FOR KEY SHARE. + */ + if (XactReadOnly && !isTempNamespace(RelationGetNamespace(pk_rel))) + PreventCommandIfReadOnly("SELECT FOR KEY SHARE"); + /* * Advance the command counter so the check sees the effects of prior * triggers in this statement, as SPI does when executing the query issued diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out index abafaff785a..e2709279eae 100644 --- a/src/test/regress/expected/foreign_key.out +++ b/src/test/regress/expected/foreign_key.out @@ -4119,3 +4119,34 @@ DROP TYPE fkint CASCADE; NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to function fkint_in(cstring) drop cascades to function fkint_out(fkint) +-- An FK check locks the referenced row, which a read-only transaction does +-- not allow unless the referenced table is temporary. +CREATE TABLE fp_pk_ro (a int PRIMARY KEY); +INSERT INTO fp_pk_ro VALUES (1); +CREATE TABLE fp_fk_ro (a int REFERENCES fp_pk_ro DEFERRABLE INITIALLY DEFERRED); +BEGIN; +INSERT INTO fp_fk_ro VALUES (1); +SET TRANSACTION READ ONLY; +COMMIT; -- fails +ERROR: cannot execute SELECT FOR KEY SHARE in a read-only transaction +CREATE TEMP TABLE fp_pk_ro_tmp (a int PRIMARY KEY); +INSERT INTO fp_pk_ro_tmp VALUES (1); +CREATE TEMP TABLE fp_fk_ro_tmp (a int REFERENCES fp_pk_ro_tmp + DEFERRABLE INITIALLY DEFERRED); +BEGIN; +INSERT INTO fp_fk_ro_tmp VALUES (1); +SET TRANSACTION READ ONLY; +COMMIT; -- succeeds +SELECT count(*) FROM fp_fk_ro; + count +------- + 0 +(1 row) + +SELECT count(*) FROM fp_fk_ro_tmp; + count +------- + 1 +(1 row) + +DROP TABLE fp_fk_ro, fp_pk_ro, fp_fk_ro_tmp, fp_pk_ro_tmp; diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql index da62f601b46..a706083948d 100644 --- a/src/test/regress/sql/foreign_key.sql +++ b/src/test/regress/sql/foreign_key.sql @@ -3065,3 +3065,24 @@ DROP TABLE pktable_inval; DROP CAST (fkint AS int4); DROP FUNCTION fkint_to_int4(fkint); DROP TYPE fkint CASCADE; + +-- An FK check locks the referenced row, which a read-only transaction does +-- not allow unless the referenced table is temporary. +CREATE TABLE fp_pk_ro (a int PRIMARY KEY); +INSERT INTO fp_pk_ro VALUES (1); +CREATE TABLE fp_fk_ro (a int REFERENCES fp_pk_ro DEFERRABLE INITIALLY DEFERRED); +BEGIN; +INSERT INTO fp_fk_ro VALUES (1); +SET TRANSACTION READ ONLY; +COMMIT; -- fails +CREATE TEMP TABLE fp_pk_ro_tmp (a int PRIMARY KEY); +INSERT INTO fp_pk_ro_tmp VALUES (1); +CREATE TEMP TABLE fp_fk_ro_tmp (a int REFERENCES fp_pk_ro_tmp + DEFERRABLE INITIALLY DEFERRED); +BEGIN; +INSERT INTO fp_fk_ro_tmp VALUES (1); +SET TRANSACTION READ ONLY; +COMMIT; -- succeeds +SELECT count(*) FROM fp_fk_ro; +SELECT count(*) FROM fp_fk_ro_tmp; +DROP TABLE fp_fk_ro, fp_pk_ro, fp_fk_ro_tmp, fp_pk_ro_tmp;