diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index 652fa4a943d..77aec0fd9eb 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -112,6 +112,20 @@ static uint32 pgfdw_we_get_result = 0; */ #define RETRY_CANCEL_TIMEOUT 1000 +/* + * Macro for constructing commit command to be sent + * + * We synchronize the read/write mode before committing remote transactions + * so deferred triggers on remote servers can run in the right mode. + */ +#define CONSTRUCT_COMMIT_COMMAND(sql, entry) \ + do { \ + if ((read_only_level > 0) && !(entry)->xact_read_only) \ + strcpy((sql), "SET TRANSACTION READ ONLY; COMMIT TRANSACTION"); \ + else \ + strcpy((sql), "COMMIT TRANSACTION"); \ + } while(0) + /* Macro for constructing abort command to be sent */ #define CONSTRUCT_ABORT_COMMAND(sql, entry, toplevel) \ do { \ @@ -942,7 +956,7 @@ begin_remote_xact(ConnCacheEntry *entry) appendStringInfoString(&sql, "REPEATABLE READ"); if (ro) appendStringInfoString(&sql, " READ ONLY"); - if (XactDeferrable) + if (XactDeferrable && PQserverVersion(entry->conn) >= 90100) appendStringInfoString(&sql, " DEFERRABLE"); entry->changing_xact_state = true; do_sql_command(entry->conn, sql.data); @@ -972,7 +986,7 @@ begin_remote_xact(ConnCacheEntry *entry) if (entry->xact_depth == read_only_level) { entry->changing_xact_state = true; - do_sql_command(entry->conn, "SET transaction_read_only = on"); + do_sql_command(entry->conn, "SET TRANSACTION READ ONLY"); entry->xact_read_only = true; entry->changing_xact_state = false; } @@ -1005,7 +1019,7 @@ begin_remote_xact(ConnCacheEntry *entry) initStringInfo(&sql); appendStringInfo(&sql, "SAVEPOINT s%d", entry->xact_depth + 1); if (ro) - appendStringInfoString(&sql, "; SET transaction_read_only = on"); + appendStringInfoString(&sql, "; SET TRANSACTION READ ONLY"); entry->changing_xact_state = true; do_sql_command(entry->conn, sql.data); entry->xact_depth++; @@ -1206,6 +1220,24 @@ pgfdw_xact_callback(XactEvent event, void *arg) if (!xact_got_connection) return; + /* + * If we are called for pre-commit cleanup, ensure read_only_level is set + * for later processing. Note that we need to do this because the local + * transaction may have become read-only since the last remote operation. + */ + if (event == XACT_EVENT_PARALLEL_PRE_COMMIT || + event == XACT_EVENT_PRE_COMMIT) + { + if (XactReadOnly) + { + if (read_only_level == 0) + read_only_level = 1; + Assert(read_only_level == 1); + } + else + Assert(read_only_level == 0); + } + /* * Scan all connection cache entries to find open remote transactions, and * close them. @@ -1222,6 +1254,8 @@ pgfdw_xact_callback(XactEvent event, void *arg) /* If it has an open remote transaction, try to close it */ if (entry->xact_depth > 0) { + char sql[100]; + elog(DEBUG3, "closing remote transaction on connection %p", entry->conn); @@ -1237,14 +1271,17 @@ pgfdw_xact_callback(XactEvent event, void *arg) pgfdw_reject_incomplete_xact_state_change(entry); /* Commit all remote transactions during pre-commit */ + CONSTRUCT_COMMIT_COMMAND(sql, entry); entry->changing_xact_state = true; if (entry->parallel_commit) { - do_sql_command_begin(entry->conn, "COMMIT TRANSACTION"); + do_sql_command_begin(entry->conn, sql); pending_entries = lappend(pending_entries, entry); continue; } - do_sql_command(entry->conn, "COMMIT TRANSACTION"); + do_sql_command(entry->conn, sql); + if ((read_only_level > 0) && !entry->xact_read_only) + entry->xact_read_only = true; entry->changing_xact_state = false; /* @@ -2041,6 +2078,8 @@ pgfdw_finish_pre_commit_cleanup(List *pending_entries) */ foreach(lc, pending_entries) { + char sql[100]; + entry = (ConnCacheEntry *) lfirst(lc); Assert(entry->changing_xact_state); @@ -2049,7 +2088,10 @@ pgfdw_finish_pre_commit_cleanup(List *pending_entries) * We might already have received the result on the socket, so pass * consume_input=true to try to consume it first */ - do_sql_command_end(entry->conn, "COMMIT TRANSACTION", true); + CONSTRUCT_COMMIT_COMMAND(sql, entry); + do_sql_command_end(entry->conn, sql, true); + if ((read_only_level > 0) && !(entry)->xact_read_only) + entry->xact_read_only = true; entry->changing_xact_state = false; /* Do a DEALLOCATE ALL in parallel if needed */ diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index fe4e6478e28..01577d8d69b 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -1148,20 +1148,17 @@ CREATE SUBSCRIPTION my_subscription SERVER subscription_server PUBLICATION testp - The remote transaction is opened in the same read/write mode as the local - transaction: if the local transaction is READ ONLY, - the remote transaction is opened in READ ONLY mode, - otherwise it is opened in READ WRITE mode. - (This rule is also applied to remote and local subtransactions.) + Local READ ONLY transactions propagate their read-only + mode to remote sessions. + (This rule is also applied to local subtransactions.) Note that this does not prevent login triggers executed on the remote server from writing. - The remote transaction is also opened in the same deferrable mode as the - local transaction: if the local transaction is DEFERRABLE, - the remote transaction is opened in DEFERRABLE mode, - otherwise it is opened in NOT DEFERRABLE mode. + Also, local DEFERRABLE transactions propagate their + deferrable mode to remote sessions. + (This rule is only applied to remote servers 9.1 and newer.)