Skip to content

Commit d651c47

Browse files
tglsfdcashcmd
authored andcommitted
Fix postgres_fdw's issues with inconsistent interpretation of data values.
For datatypes whose output formatting depends on one or more GUC settings, we have to worry about whether the other server will interpret the value the same way it was meant. pg_dump has been aware of this hazard for a long time, but postgres_fdw needs to deal with it too. To fix data retrieval from the remote server, set the necessary remote GUC settings at connection startup. (We were already assuming that settings made then would persist throughout the remote session.) To fix data transmission to the remote server, temporarily force the relevant GUCs to the right values when we're about to convert any data values to text for transmission. This is all pretty grotty, and not very cheap either. It's tempting to think of defining one uber-GUC that would override any settings that might render printed data values unportable. But of course, older remote servers wouldn't know any such thing and would still need this logic. While at it, revert commit f7951ee, since this provides a real fix. (The timestamptz given in the error message returned from the "remote" server will now reliably be shown in UTC.)
1 parent 837bef3 commit d651c47

File tree

4 files changed

+116
-26
lines changed

4 files changed

+116
-26
lines changed

contrib/postgres_fdw/connection.c

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ static bool xact_got_connection = false;
6464
static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
6565
static void check_conn_params(const char **keywords, const char **values);
6666
static void configure_remote_session(PGconn *conn);
67+
static void do_sql_command(PGconn *conn, const char *sql);
6768
static void begin_remote_xact(ConnCacheEntry *entry);
6869
static void pgfdw_xact_callback(XactEvent event, void *arg);
6970
static void pgfdw_subxact_callback(SubXactEvent event,
@@ -299,11 +300,43 @@ check_conn_params(const char **keywords, const char **values)
299300
static void
300301
configure_remote_session(PGconn *conn)
301302
{
302-
const char *sql;
303-
PGresult *res;
303+
int remoteversion = PQserverVersion(conn);
304304

305305
/* Force the search path to contain only pg_catalog (see deparse.c) */
306-
sql = "SET search_path = pg_catalog";
306+
do_sql_command(conn, "SET search_path = pg_catalog");
307+
308+
/*
309+
* Set remote timezone; this is basically just cosmetic, since all
310+
* transmitted and returned timestamptzs should specify a zone explicitly
311+
* anyway. However it makes the regression test outputs more predictable.
312+
*
313+
* We don't risk setting remote zone equal to ours, since the remote
314+
* server might use a different timezone database.
315+
*/
316+
do_sql_command(conn, "SET timezone = UTC");
317+
318+
/*
319+
* Set values needed to ensure unambiguous data output from remote. (This
320+
* logic should match what pg_dump does. See also set_transmission_modes
321+
* in postgres_fdw.c.)
322+
*/
323+
do_sql_command(conn, "SET datestyle = ISO");
324+
if (remoteversion >= 80400)
325+
do_sql_command(conn, "SET intervalstyle = postgres");
326+
if (remoteversion >= 90000)
327+
do_sql_command(conn, "SET extra_float_digits = 3");
328+
else
329+
do_sql_command(conn, "SET extra_float_digits = 2");
330+
}
331+
332+
/*
333+
* Convenience subroutine to issue a non-data-returning SQL command to remote
334+
*/
335+
static void
336+
do_sql_command(PGconn *conn, const char *sql)
337+
{
338+
PGresult *res;
339+
307340
res = PQexec(conn, sql);
308341
if (PQresultStatus(res) != PGRES_COMMAND_OK)
309342
pgfdw_report_error(ERROR, res, true, sql);
@@ -324,7 +357,6 @@ static void
324357
begin_remote_xact(ConnCacheEntry *entry)
325358
{
326359
int curlevel = GetCurrentTransactionNestLevel();
327-
PGresult *res;
328360

329361
/* Start main transaction if we haven't yet */
330362
if (entry->xact_depth <= 0)
@@ -338,10 +370,7 @@ begin_remote_xact(ConnCacheEntry *entry)
338370
sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE";
339371
else
340372
sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ";
341-
res = PQexec(entry->conn, sql);
342-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
343-
pgfdw_report_error(ERROR, res, true, sql);
344-
PQclear(res);
373+
do_sql_command(entry->conn, sql);
345374
entry->xact_depth = 1;
346375
}
347376

@@ -355,10 +384,7 @@ begin_remote_xact(ConnCacheEntry *entry)
355384
char sql[64];
356385

357386
snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1);
358-
res = PQexec(entry->conn, sql);
359-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
360-
pgfdw_report_error(ERROR, res, true, sql);
361-
PQclear(res);
387+
do_sql_command(entry->conn, sql);
362388
entry->xact_depth++;
363389
}
364390
}
@@ -475,11 +501,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
475501
switch (event)
476502
{
477503
case XACT_EVENT_COMMIT:
478-
/* Commit all remote transactions during pre-commit */
479-
res = PQexec(entry->conn, "COMMIT TRANSACTION");
480-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
481-
pgfdw_report_error(ERROR, res, true, "COMMIT TRANSACTION");
482-
PQclear(res);
504+
/* Commit all remote transactions */
505+
do_sql_command(entry->conn, "COMMIT TRANSACTION");
483506
break;
484507
case XACT_EVENT_PREPARE:
485508
/* Should not get here -- pre-commit should have handled it */
@@ -564,15 +587,15 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
564587
elog(ERROR, "missed cleaning up remote subtransaction at level %d",
565588
entry->xact_depth);
566589

567-
/* Rollback all remote subtransactions during abort */
568-
snprintf(sql, sizeof(sql),
569-
"ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d",
570-
curlevel, curlevel);
571-
res = PQexec(entry->conn, sql);
572-
if (PQresultStatus(res) != PGRES_COMMAND_OK)
573-
pgfdw_report_error(WARNING, res, true, sql);
574-
else
575-
PQclear(res);
590+
/* Rollback all remote subtransactions during abort */
591+
snprintf(sql, sizeof(sql),
592+
"ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d",
593+
curlevel, curlevel);
594+
res = PQexec(entry->conn, sql);
595+
if (PQresultStatus(res) != PGRES_COMMAND_OK)
596+
pgfdw_report_error(WARNING, res, true, sql);
597+
else
598+
PQclear(res);
576599

577600
/* OK, we're outta that level of subtransaction */
578601
entry->xact_depth--;

contrib/postgres_fdw/deparse.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,8 +436,12 @@ appendWhereClause(StringInfo buf,
436436
List *exprs,
437437
PlannerInfo *root)
438438
{
439+
int nestlevel;
439440
ListCell *lc;
440441

442+
/* Make sure any constants in the exprs are printed portably */
443+
nestlevel = set_transmission_modes();
444+
441445
foreach(lc, exprs)
442446
{
443447
RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
@@ -454,6 +458,8 @@ appendWhereClause(StringInfo buf,
454458

455459
is_first = false;
456460
}
461+
462+
reset_transmission_modes(nestlevel);
457463
}
458464

459465
/*

contrib/postgres_fdw/postgres_fdw.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "optimizer/pathnode.h"
2626
#include "optimizer/planmain.h"
2727
#include "parser/parsetree.h"
28+
#include "utils/builtins.h"
29+
#include "utils/guc.h"
2830
#include "utils/lsyscache.h"
2931
#include "utils/memutils.h"
3032

@@ -907,9 +909,12 @@ create_cursor(ForeignScanState *node)
907909
if (numParams > 0 && !festate->extparams_done)
908910
{
909911
ParamListInfo params = node->ss.ps.state->es_param_list_info;
912+
int nestlevel;
910913
List *param_numbers;
911914
ListCell *lc;
912915

916+
nestlevel = set_transmission_modes();
917+
913918
param_numbers = (List *)
914919
list_nth(festate->fdw_private, FdwPrivateExternParamIds);
915920
foreach(lc, param_numbers)
@@ -951,6 +956,8 @@ create_cursor(ForeignScanState *node)
951956
prm->value);
952957
}
953958
}
959+
reset_transmission_modes(nestlevel);
960+
954961
festate->extparams_done = true;
955962
}
956963

@@ -1058,6 +1065,56 @@ fetch_more_data(ForeignScanState *node)
10581065
MemoryContextSwitchTo(oldcontext);
10591066
}
10601067

1068+
/*
1069+
* Force assorted GUC parameters to settings that ensure that we'll output
1070+
* data values in a form that is unambiguous to the remote server.
1071+
*
1072+
* This is rather expensive and annoying to do once per row, but there's
1073+
* little choice if we want to be sure values are transmitted accurately;
1074+
* we can't leave the settings in place between rows for fear of affecting
1075+
* user-visible computations.
1076+
*
1077+
* We use the equivalent of a function SET option to allow the settings to
1078+
* persist only until the caller calls reset_transmission_modes(). If an
1079+
* error is thrown in between, guc.c will take care of undoing the settings.
1080+
*
1081+
* The return value is the nestlevel that must be passed to
1082+
* reset_transmission_modes() to undo things.
1083+
*/
1084+
int
1085+
set_transmission_modes(void)
1086+
{
1087+
int nestlevel = NewGUCNestLevel();
1088+
1089+
/*
1090+
* The values set here should match what pg_dump does. See also
1091+
* configure_remote_session in connection.c.
1092+
*/
1093+
if (DateStyle != USE_ISO_DATES)
1094+
(void) set_config_option("datestyle", "ISO",
1095+
PGC_USERSET, PGC_S_SESSION,
1096+
GUC_ACTION_SAVE, true, 0);
1097+
if (IntervalStyle != INTSTYLE_POSTGRES)
1098+
(void) set_config_option("intervalstyle", "postgres",
1099+
PGC_USERSET, PGC_S_SESSION,
1100+
GUC_ACTION_SAVE, true, 0);
1101+
if (extra_float_digits < 3)
1102+
(void) set_config_option("extra_float_digits", "3",
1103+
PGC_USERSET, PGC_S_SESSION,
1104+
GUC_ACTION_SAVE, true, 0);
1105+
1106+
return nestlevel;
1107+
}
1108+
1109+
/*
1110+
* Undo the effects of set_transmission_modes().
1111+
*/
1112+
void
1113+
reset_transmission_modes(int nestlevel)
1114+
{
1115+
AtEOXact_GUC(true, nestlevel);
1116+
}
1117+
10611118
/*
10621119
* Utility routine to close a cursor.
10631120
*/

contrib/postgres_fdw/postgres_fdw.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020

2121
#include "libpq-fe.h"
2222

23+
/* in postgres_fdw.c */
24+
extern int set_transmission_modes(void);
25+
extern void reset_transmission_modes(int nestlevel);
26+
2327
/* in connection.c */
2428
extern PGconn *GetConnection(ForeignServer *server, UserMapping *user);
2529
extern void ReleaseConnection(PGconn *conn);

0 commit comments

Comments
 (0)