Skip to content

Commit 681e687

Browse files
Etsuro FujitaCommitfest Bot
authored andcommitted
postgres_fdw: Inherit the local transaction's access/deferrable modes.
Previously, postgres_fdw always 1) opened a remote transaction in READ WRITE mode even when the local transaction was READ ONLY, causing a READ ONLY transaction using it that references a foreign table mapped to a remote view executing a volatile function to write in the remote side, and 2) opened the remote transaction in NOT DEFERRABLE mode even when the local transaction was DEFERRABLE, causing a SERIALIZABLE READ ONLY DEFERRABLE transaction using it to abort due to a serialization failure in the remote side. To avoid these, modify postgres_fdw to open a remote transaction in the same access/deferrable modes as the local transaction. This commit also modifies it to open a remote subtransaction in the same access mode as the local subtransaction. Although these issues exist since the introduction of postgres_fdw, there have been no reports from the field. So it seems fine to just fix them in master only. Author: Etsuro Fujita <[email protected]> Reviewed-by: Ashutosh Bapat <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/CAPmGK16n_hcUUWuOdmeUS%2Bw4Q6dZvTEDHb%3DOP%3D5JBzo-M3QmpQ%40mail.gmail.com
1 parent 5761d99 commit 681e687

File tree

6 files changed

+347
-8
lines changed

6 files changed

+347
-8
lines changed

contrib/postgres_fdw/connection.c

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ typedef struct ConnCacheEntry
5858
/* Remaining fields are invalid when conn is NULL: */
5959
int xact_depth; /* 0 = no xact open, 1 = main xact open, 2 =
6060
* one level of subxact open, etc */
61+
bool xact_read_only; /* xact r/o state */
6162
bool have_prep_stmt; /* have we prepared any stmts in this xact? */
6263
bool have_error; /* have any subxacts aborted in this xact? */
6364
bool changing_xact_state; /* xact state change in process */
@@ -84,6 +85,12 @@ static unsigned int prep_stmt_number = 0;
8485
/* tracks whether any work is needed in callback functions */
8586
static bool xact_got_connection = false;
8687

88+
/*
89+
* tracks the nesting level of the topmost read-only transaction determined
90+
* by GetTopReadOnlyTransactionNestLevel()
91+
*/
92+
static int top_read_only_level = 0;
93+
8794
/* custom wait event values, retrieved from shared memory */
8895
static uint32 pgfdw_we_cleanup_result = 0;
8996
static uint32 pgfdw_we_connect = 0;
@@ -374,6 +381,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
374381

375382
/* Reset all transient state fields, to be sure all are clean */
376383
entry->xact_depth = 0;
384+
entry->xact_read_only = false;
377385
entry->have_prep_stmt = false;
378386
entry->have_error = false;
379387
entry->changing_xact_state = false;
@@ -848,29 +856,81 @@ do_sql_command_end(PGconn *conn, const char *sql, bool consume_input)
848856
* those scans. A disadvantage is that we can't provide sane emulation of
849857
* READ COMMITTED behavior --- it would be nice if we had some other way to
850858
* control which remote queries share a snapshot.
859+
*
860+
* Note also that we always start the remote transaction with the same
861+
* read/write and deferrable properties as the local transaction, and start
862+
* the remote subtransaction with the same read/write property as the local
863+
* subtransaction.
851864
*/
852865
static void
853866
begin_remote_xact(ConnCacheEntry *entry)
854867
{
855868
int curlevel = GetCurrentTransactionNestLevel();
856869

857-
/* Start main transaction if we haven't yet */
870+
/*
871+
* Set the nesting level of the topmost read-only transaction if the
872+
* current transaction is read-only and we haven't yet. Once it's set,
873+
* it's retained until that transaction is committed/aborted, and then
874+
* reset (see pgfdw_xact_callback and pgfdw_subxact_callback).
875+
*/
876+
if (XactReadOnly)
877+
{
878+
if (top_read_only_level == 0)
879+
top_read_only_level = GetTopReadOnlyTransactionNestLevel();
880+
Assert(top_read_only_level > 0);
881+
}
882+
else
883+
Assert(top_read_only_level == 0);
884+
885+
/*
886+
* Start main transaction if we haven't yet; otherwise, change the
887+
* already-started remote transaction/subtransaction to read-only if the
888+
* local transaction/subtransaction have been done so after starting them
889+
* and we haven't yet.
890+
*/
858891
if (entry->xact_depth <= 0)
859892
{
860-
const char *sql;
893+
StringInfoData sql;
894+
bool ro = (top_read_only_level == 1);
861895

862896
elog(DEBUG3, "starting remote transaction on connection %p",
863897
entry->conn);
864898

899+
initStringInfo(&sql);
900+
appendStringInfoString(&sql, "START TRANSACTION ISOLATION LEVEL ");
865901
if (IsolationIsSerializable())
866-
sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE";
902+
appendStringInfoString(&sql, "SERIALIZABLE");
867903
else
868-
sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ";
904+
appendStringInfoString(&sql, "REPEATABLE READ");
905+
if (ro)
906+
appendStringInfoString(&sql, " READ ONLY");
907+
if (XactDeferrable)
908+
appendStringInfoString(&sql, " DEFERRABLE");
869909
entry->changing_xact_state = true;
870-
do_sql_command(entry->conn, sql);
910+
do_sql_command(entry->conn, sql.data);
871911
entry->xact_depth = 1;
912+
if (ro)
913+
{
914+
Assert(!entry->xact_read_only);
915+
entry->xact_read_only = true;
916+
}
872917
entry->changing_xact_state = false;
873918
}
919+
else if (!entry->xact_read_only)
920+
{
921+
Assert(top_read_only_level == 0 ||
922+
entry->xact_depth <= top_read_only_level);
923+
if (entry->xact_depth == top_read_only_level)
924+
{
925+
entry->changing_xact_state = true;
926+
do_sql_command(entry->conn, "SET transaction_read_only = on");
927+
entry->xact_read_only = true;
928+
entry->changing_xact_state = false;
929+
}
930+
}
931+
else
932+
Assert(top_read_only_level > 0 &&
933+
entry->xact_depth >= top_read_only_level);
874934

875935
/*
876936
* If we're in a subtransaction, stack up savepoints to match our level.
@@ -879,12 +939,21 @@ begin_remote_xact(ConnCacheEntry *entry)
879939
*/
880940
while (entry->xact_depth < curlevel)
881941
{
882-
char sql[64];
942+
StringInfoData sql;
943+
bool ro = (entry->xact_depth + 1 == top_read_only_level);
883944

884-
snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1);
945+
initStringInfo(&sql);
946+
appendStringInfo(&sql, "SAVEPOINT s%d", entry->xact_depth + 1);
947+
if (ro)
948+
appendStringInfoString(&sql, "; SET transaction_read_only = on");
885949
entry->changing_xact_state = true;
886-
do_sql_command(entry->conn, sql);
950+
do_sql_command(entry->conn, sql.data);
887951
entry->xact_depth++;
952+
if (ro)
953+
{
954+
Assert(!entry->xact_read_only);
955+
entry->xact_read_only = true;
956+
}
888957
entry->changing_xact_state = false;
889958
}
890959
}
@@ -1189,6 +1258,9 @@ pgfdw_xact_callback(XactEvent event, void *arg)
11891258

11901259
/* Also reset cursor numbering for next transaction */
11911260
cursor_number = 0;
1261+
1262+
/* Likewise for top_read_only_level */
1263+
top_read_only_level = 0;
11921264
}
11931265

11941266
/*
@@ -1287,6 +1359,10 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
12871359
false);
12881360
}
12891361
}
1362+
1363+
/* If in the topmost read-only transaction, reset top_read_only_level */
1364+
if (curlevel == top_read_only_level)
1365+
top_read_only_level = 0;
12901366
}
12911367

12921368
/*
@@ -1389,6 +1465,9 @@ pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel)
13891465
/* Reset state to show we're out of a transaction */
13901466
entry->xact_depth = 0;
13911467

1468+
/* Reset xact r/o state */
1469+
entry->xact_read_only = false;
1470+
13921471
/*
13931472
* If the connection isn't in a good idle state, it is marked as
13941473
* invalid or keep_connections option of its server is disabled, then
@@ -1409,6 +1488,10 @@ pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel)
14091488
{
14101489
/* Reset state to show we're out of a subtransaction */
14111490
entry->xact_depth--;
1491+
1492+
/* If in the topmost read-only transaction, reset xact r/o state */
1493+
if (entry->xact_depth + 1 == top_read_only_level)
1494+
entry->xact_read_only = false;
14121495
}
14131496
}
14141497

contrib/postgres_fdw/expected/postgres_fdw.out

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12405,6 +12405,140 @@ SELECT count(*) FROM remote_application_name
1240512405
DROP FOREIGN TABLE remote_application_name;
1240612406
DROP VIEW my_application_name;
1240712407
-- ===================================================================
12408+
-- test read-only and/or deferrable transactions
12409+
-- ===================================================================
12410+
CREATE TABLE loct (f1 int, f2 text);
12411+
CREATE FUNCTION locf() RETURNS SETOF loct LANGUAGE SQL AS
12412+
'UPDATE public.loct SET f2 = f2 || f2 RETURNING *';
12413+
CREATE VIEW locv AS SELECT t.* FROM locf() t;
12414+
CREATE FOREIGN TABLE remt (f1 int, f2 text)
12415+
SERVER loopback OPTIONS (table_name 'locv');
12416+
CREATE FOREIGN TABLE remt2 (f1 int, f2 text)
12417+
SERVER loopback2 OPTIONS (table_name 'locv');
12418+
INSERT INTO loct VALUES (1, 'foo'), (2, 'bar');
12419+
START TRANSACTION READ ONLY;
12420+
SAVEPOINT s;
12421+
SELECT * FROM remt; -- should fail
12422+
ERROR: cannot execute UPDATE in a read-only transaction
12423+
CONTEXT: SQL function "locf" statement 1
12424+
remote SQL command: SELECT f1, f2 FROM public.locv
12425+
ROLLBACK TO s;
12426+
RELEASE SAVEPOINT s;
12427+
SELECT * FROM remt; -- should fail
12428+
ERROR: cannot execute UPDATE in a read-only transaction
12429+
CONTEXT: SQL function "locf" statement 1
12430+
remote SQL command: SELECT f1, f2 FROM public.locv
12431+
ROLLBACK;
12432+
START TRANSACTION;
12433+
SAVEPOINT s;
12434+
SET transaction_read_only = on;
12435+
SELECT * FROM remt; -- should fail
12436+
ERROR: cannot execute UPDATE in a read-only transaction
12437+
CONTEXT: SQL function "locf" statement 1
12438+
remote SQL command: SELECT f1, f2 FROM public.locv
12439+
ROLLBACK TO s;
12440+
RELEASE SAVEPOINT s;
12441+
SET transaction_read_only = on;
12442+
SELECT * FROM remt; -- should fail
12443+
ERROR: cannot execute UPDATE in a read-only transaction
12444+
CONTEXT: SQL function "locf" statement 1
12445+
remote SQL command: SELECT f1, f2 FROM public.locv
12446+
ROLLBACK;
12447+
START TRANSACTION;
12448+
SAVEPOINT s;
12449+
SELECT * FROM remt; -- should work
12450+
f1 | f2
12451+
----+--------
12452+
1 | foofoo
12453+
2 | barbar
12454+
(2 rows)
12455+
12456+
SET transaction_read_only = on;
12457+
SELECT * FROM remt; -- should fail
12458+
ERROR: cannot execute UPDATE in a read-only transaction
12459+
CONTEXT: SQL function "locf" statement 1
12460+
remote SQL command: SELECT f1, f2 FROM public.locv
12461+
ROLLBACK TO s;
12462+
RELEASE SAVEPOINT s;
12463+
SELECT * FROM remt; -- should work
12464+
f1 | f2
12465+
----+--------
12466+
1 | foofoo
12467+
2 | barbar
12468+
(2 rows)
12469+
12470+
SET transaction_read_only = on;
12471+
SELECT * FROM remt; -- should fail
12472+
ERROR: cannot execute UPDATE in a read-only transaction
12473+
CONTEXT: SQL function "locf" statement 1
12474+
remote SQL command: SELECT f1, f2 FROM public.locv
12475+
ROLLBACK;
12476+
START TRANSACTION;
12477+
SAVEPOINT s;
12478+
SELECT * FROM remt; -- should work
12479+
f1 | f2
12480+
----+--------
12481+
1 | foofoo
12482+
2 | barbar
12483+
(2 rows)
12484+
12485+
SET transaction_read_only = on;
12486+
SELECT * FROM remt2; -- should fail
12487+
ERROR: cannot execute UPDATE in a read-only transaction
12488+
CONTEXT: SQL function "locf" statement 1
12489+
remote SQL command: SELECT f1, f2 FROM public.locv
12490+
ROLLBACK TO s;
12491+
RELEASE SAVEPOINT s;
12492+
SELECT * FROM remt; -- should work
12493+
f1 | f2
12494+
----+--------
12495+
1 | foofoo
12496+
2 | barbar
12497+
(2 rows)
12498+
12499+
SET transaction_read_only = on;
12500+
SELECT * FROM remt2; -- should fail
12501+
ERROR: cannot execute UPDATE in a read-only transaction
12502+
CONTEXT: SQL function "locf" statement 1
12503+
remote SQL command: SELECT f1, f2 FROM public.locv
12504+
ROLLBACK;
12505+
DROP FOREIGN TABLE remt;
12506+
CREATE FOREIGN TABLE remt (f1 int, f2 text)
12507+
SERVER loopback OPTIONS (table_name 'loct');
12508+
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
12509+
SELECT * FROM remt;
12510+
f1 | f2
12511+
----+-----
12512+
1 | foo
12513+
2 | bar
12514+
(2 rows)
12515+
12516+
COMMIT;
12517+
START TRANSACTION ISOLATION LEVEL SERIALIZABLE DEFERRABLE;
12518+
SELECT * FROM remt;
12519+
f1 | f2
12520+
----+-----
12521+
1 | foo
12522+
2 | bar
12523+
(2 rows)
12524+
12525+
COMMIT;
12526+
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
12527+
SELECT * FROM remt;
12528+
f1 | f2
12529+
----+-----
12530+
1 | foo
12531+
2 | bar
12532+
(2 rows)
12533+
12534+
COMMIT;
12535+
-- Clean up
12536+
DROP FOREIGN TABLE remt;
12537+
DROP FOREIGN TABLE remt2;
12538+
DROP VIEW locv;
12539+
DROP FUNCTION locf();
12540+
DROP TABLE loct;
12541+
-- ===================================================================
1240812542
-- test parallel commit and parallel abort
1240912543
-- ===================================================================
1241012544
ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true');

0 commit comments

Comments
 (0)