1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Refuse RI fast-path row locks in read-only transactions

The SPI path checks a foreign key with SELECT ... FOR KEY SHARE, which
ExecCheckXactReadOnly() refuses in a read-only transaction unless the
referenced table is temporary.  The fast path locks the referenced row
without that check, so for example a deferred check that fires at
COMMIT after SET TRANSACTION READ ONLY succeeded, having locked a row
in a read-only transaction, where the SPI path fails with "cannot
execute SELECT FOR KEY SHARE in a read-only transaction".

Fix the fast path to refuse it the same way so the behavior matches
with checks going through SPI.

Discussion: https://postgr.es/m/CA+HiwqG79XK1oObdZ2AwT660CeJ6s3Mn4LrFPCme-k4L2rF_ag@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
Amit Langote
2026-10-08 09:26:21 +09:00
parent 3f5bfbce4c
commit 2f840afbf5
3 changed files with 62 additions and 0 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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;