/* * Start remote transaction or subtransaction, if needed. * * Note that we always use at least REPEATABLE READ in the remote session. * This is so that, if a query initiates multiple scans of the same or * different foreign tables, we will get snapshot-consistent results from * those scans. A disadvantage is that we can't provide sane emulation of * READ COMMITTED behavior --- it would be nice if we had some other way to * control which remote queries share a snapshot. */ static void begin_remote_xact(ConnCacheEntry *entry) { int curlevel = GetCurrentTransactionNestLevel(); /* Start main transaction if we haven't yet */ if (entry->xact_depth <= 0) { const char *sql; elog(DEBUG3, "starting remote transaction on connection %p", entry->conn); if (IsolationIsSerializable()) sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE"; else sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ"; do_sql_command(entry->conn, sql); entry->xact_depth = 1; } /* * If we're in a subtransaction, stack up savepoints to match our level. * This ensures we can rollback just the desired effects when a * subtransaction aborts. */ while (entry->xact_depth < curlevel) { char sql[64]; snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1); do_sql_command(entry->conn, sql); entry->xact_depth++; } }
/* * Issue SET commands to make sure remote session is configured properly. * * We do this just once at connection, assuming nothing will change the * values later. Since we'll never send volatile function calls to the * remote, there shouldn't be any way to break this assumption from our end. * It's possible to think of ways to break it at the remote end, eg making * a foreign table point to a view that includes a set_config call --- * but once you admit the possibility of a malicious view definition, * there are any number of ways to break things. */ static void configure_remote_session(PGconn *conn) { int remoteversion = PQserverVersion(conn); /* Force the search path to contain only pg_catalog (see deparse.c) */ do_sql_command(conn, "SET search_path = pg_catalog"); /* * Set remote timezone; this is basically just cosmetic, since all * transmitted and returned timestamptzs should specify a zone explicitly * anyway. However it makes the regression test outputs more predictable. * * We don't risk setting remote zone equal to ours, since the remote * server might use a different timezone database. Instead, use UTC * (quoted, because very old servers are picky about case). */ do_sql_command(conn, "SET timezone = 'UTC'"); /* * Set values needed to ensure unambiguous data output from remote. (This * logic should match what pg_dump does. See also set_transmission_modes * in postgres_fdw.c.) */ do_sql_command(conn, "SET datestyle = ISO"); if (remoteversion >= 80400) do_sql_command(conn, "SET intervalstyle = postgres"); if (remoteversion >= 90000) do_sql_command(conn, "SET extra_float_digits = 3"); else do_sql_command(conn, "SET extra_float_digits = 2"); }
/* * pgfdw_subxact_callback --- cleanup at subtransaction end. */ static void pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; int curlevel; /* Nothing to do at subxact start, nor after commit. */ if (!(event == SUBXACT_EVENT_PRE_COMMIT_SUB || event == SUBXACT_EVENT_ABORT_SUB)) return; /* Quick exit if no connections were touched in this transaction. */ if (!xact_got_connection) return; /* * Scan all connection cache entries to find open remote subtransactions * of the current level, and close them. */ curlevel = GetCurrentTransactionNestLevel(); hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { PGresult *res; char sql[100]; /* * We only care about connections with open remote subtransactions of * the current level. */ if (entry->conn == NULL || entry->xact_depth < curlevel) continue; if (entry->xact_depth > curlevel) elog(ERROR, "missed cleaning up remote subtransaction at level %d", entry->xact_depth); if (event == SUBXACT_EVENT_PRE_COMMIT_SUB) { /* Commit all remote subtransactions during pre-commit */ snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel); do_sql_command(entry->conn, sql); } else { /* Assume we might have lost track of prepared statements */ entry->have_error = true; /* Rollback all remote subtransactions during abort */ snprintf(sql, sizeof(sql), "ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d", curlevel, curlevel); res = PQexec(entry->conn, sql); if (PQresultStatus(res) != PGRES_COMMAND_OK) pgfdw_report_error(WARNING, res, true, sql); else PQclear(res); } /* OK, we're outta that level of subtransaction */ entry->xact_depth--; } }
/* * pgfdw_xact_callback --- cleanup at main-transaction end. */ static void pgfdw_xact_callback(XactEvent event, void *arg) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; /* Quick exit if no connections were touched in this transaction. */ if (!xact_got_connection) return; /* * Scan all connection cache entries to find open remote transactions, and * close them. */ hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { PGresult *res; /* We only care about connections with open remote transactions */ if (entry->conn == NULL || entry->xact_depth == 0) continue; elog(DEBUG3, "closing remote transaction on connection %p", entry->conn); switch (event) { case XACT_EVENT_PRE_COMMIT: /* Commit all remote transactions during pre-commit */ do_sql_command(entry->conn, "COMMIT TRANSACTION"); /* * If there were any errors in subtransactions, and we made * prepared statements, do a DEALLOCATE ALL to make sure we * get rid of all prepared statements. This is annoying and * not terribly bulletproof, but it's probably not worth * trying harder. * * DEALLOCATE ALL only exists in 8.3 and later, so this * constrains how old a server postgres_fdw can communicate * with. We intentionally ignore errors in the DEALLOCATE, so * that we can hobble along to some extent with older servers * (leaking prepared statements as we go; but we don't really * support update operations pre-8.3 anyway). */ if (entry->have_prep_stmt && entry->have_error) { res = PQexec(entry->conn, "DEALLOCATE ALL"); PQclear(res); } entry->have_prep_stmt = false; entry->have_error = false; break; case XACT_EVENT_PRE_PREPARE: /* * We disallow remote transactions that modified anything, * since it's not really reasonable to hold them open until * the prepared transaction is committed. For the moment, * throw error unconditionally; later we might allow read-only * cases. Note that the error will cause us to come right * back here with event == XACT_EVENT_ABORT, so we'll clean up * the connection state at that point. */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot prepare a transaction that modified remote tables"))); break; case XACT_EVENT_COMMIT: case XACT_EVENT_PREPARE: /* Should not get here -- pre-commit should have handled it */ elog(ERROR, "missed cleaning up connection during pre-commit"); break; case XACT_EVENT_ABORT: /* Assume we might have lost track of prepared statements */ entry->have_error = true; /* If we're aborting, abort all remote transactions too */ res = PQexec(entry->conn, "ABORT TRANSACTION"); /* Note: can't throw ERROR, it would be infinite loop */ if (PQresultStatus(res) != PGRES_COMMAND_OK) pgfdw_report_error(WARNING, res, true, "ABORT TRANSACTION"); else { PQclear(res); /* As above, make sure we've cleared any prepared stmts */ if (entry->have_prep_stmt && entry->have_error) { res = PQexec(entry->conn, "DEALLOCATE ALL"); PQclear(res); } entry->have_prep_stmt = false; entry->have_error = false; } break; } /* Reset state to show we're out of a transaction */ entry->xact_depth = 0; /* * If the connection isn't in a good idle state, discard it to * recover. Next GetConnection will open a new connection. */ if (PQstatus(entry->conn) != CONNECTION_OK || PQtransactionStatus(entry->conn) != PQTRANS_IDLE) { elog(DEBUG3, "discarding connection %p", entry->conn); PQfinish(entry->conn); entry->conn = NULL; } } /* * Regardless of the event type, we can now mark ourselves as out of the * transaction. (Note: if we are here during PRE_COMMIT or PRE_PREPARE, * this saves a useless scan of the hashtable during COMMIT or PREPARE.) */ xact_got_connection = false; /* Also reset cursor numbering for next transaction */ cursor_number = 0; }
/* * pgfdw_subxact_callback --- cleanup at subtransaction end. */ static void pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; int curlevel; /* Nothing to do at subxact start, nor after commit. */ if (!(event == SUBXACT_EVENT_PRE_COMMIT_SUB || event == SUBXACT_EVENT_ABORT_SUB)) return; /* Quick exit if no connections were touched in this transaction. */ if (!xact_got_connection) return; /* * Scan all connection cache entries to find open remote subtransactions * of the current level, and close them. */ curlevel = GetCurrentTransactionNestLevel(); hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { PGresult *res; char sql[100]; /* * We only care about connections with open remote subtransactions of * the current level. */ if (entry->conn == NULL || entry->xact_depth < curlevel) continue; if (entry->xact_depth > curlevel) elog(ERROR, "missed cleaning up remote subtransaction at level %d", entry->xact_depth); if (event == SUBXACT_EVENT_PRE_COMMIT_SUB) { /* Commit all remote subtransactions during pre-commit */ snprintf(sql, sizeof(sql), "RELEASE SAVEPOINT s%d", curlevel); do_sql_command(entry->conn, sql); } else { /* Assume we might have lost track of prepared statements */ entry->have_error = true; /* * If a command has been submitted to the remote server by using * an asynchronous execution function, the command might not have * yet completed. Check to see if a command is still being * processed by the remote server, and if so, request cancellation * of the command. */ if (PQtransactionStatus(entry->conn) == PQTRANS_ACTIVE) { PGcancel *cancel; char errbuf[256]; if ((cancel = PQgetCancel(entry->conn))) { if (!PQcancel(cancel, errbuf, sizeof(errbuf))) ereport(WARNING, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg("could not send cancel request: %s", errbuf))); PQfreeCancel(cancel); } } /* Rollback all remote subtransactions during abort */ snprintf(sql, sizeof(sql), "ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d", curlevel, curlevel); res = PQexec(entry->conn, sql); if (PQresultStatus(res) != PGRES_COMMAND_OK) pgfdw_report_error(WARNING, res, entry->conn, true, sql); else PQclear(res); } /* OK, we're outta that level of subtransaction */ entry->xact_depth--; } }