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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user