dblink: perform local-GUC modification to parse GUC-sensitive types Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but taking into account that a dblink caller may choose to cause arbitrary changes to DateStyle and IntervalStyle. To handle this, it is necessary to use PQparameterStatus before parsing any input, every time. *** a/contrib/dblink/dblink.c --- b/contrib/dblink/dblink.c *************** *** 53,58 **** --- 53,59 ---- #include "utils/acl.h" #include "utils/builtins.h" #include "utils/fmgroids.h" + #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" *************** *** 69,74 **** typedef struct remoteConn --- 70,94 ---- bool newXactForCursor; /* Opened a transaction for a cursor */ } remoteConn; + + const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"}; + + /* + * Contains information to save and restore GUCs for types with + * GUC-sensitive parsing. + */ + typedef struct remoteGucs + { + /* + * GUC nesting level. Set to -1 if no GUC nesting level has been + * introduced. + */ + int localGUCNestLevel; + + /* Kept around for PQparameterStatus to interrogate remote GUCs */ + PGconn *conn; + } remoteGucs; + typedef struct storeInfo { FunctionCallInfo fcinfo; *************** *** 118,123 **** static void validate_pkattnums(Relation rel, --- 138,146 ---- int **pkattnums, int *pknumatts); static bool is_valid_dblink_option(const PQconninfoOption *options, const char *option, Oid context); + static void initRemoteGucs(remoteGucs *rgs, PGconn *conn); + static void applyRemoteGucs(remoteGucs *rgs); + static void restoreLocalGucs(remoteGucs *rgs); /* Global */ static remoteConn *pconn = NULL; *************** *** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS) --- 554,560 ---- char *curname = NULL; int howmany = 0; bool fail = true; /* default to backward compatible */ + remoteGucs rgs; prepTuplestoreResult(fcinfo); *************** *** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS) errmsg("cursor \"%s\" does not exist", curname))); } ! materializeResult(fcinfo, res); return (Datum) 0; } --- 629,656 ---- errmsg("cursor \"%s\" does not exist", curname))); } ! /* ! * Materialize the result, before doing so set GUCs that may ! * affect parsing and then un-set them afterwards. ! */ ! initRemoteGucs(&rgs, conn); ! ! PG_TRY(); ! { ! applyRemoteGucs(&rgs); ! materializeResult(fcinfo, res); ! } ! PG_CATCH(); ! { ! /* Pop any set GUCs, if necessary */ ! restoreLocalGucs(&rgs); ! ! PG_RE_THROW(); ! } ! PG_END_TRY(); ! ! restoreLocalGucs(&rgs); ! return (Datum) 0; } *************** *** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS) static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) { ! PGconn *volatile conn = NULL; ! volatile bool freeconn = false; prepTuplestoreResult(fcinfo); DBLINK_INIT; --- 701,712 ---- static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) { ! PGconn *volatile conn = NULL; ! volatile bool freeconn = false; ! remoteGucs rgs; prepTuplestoreResult(fcinfo); + initRemoteGucs(&rgs, NULL); DBLINK_INIT; *************** *** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) --- 775,790 ---- if (!conn) DBLINK_CONN_NOT_AVAIL; + initRemoteGucs(&rgs, conn); + if (!is_async) { + /* + * Before parsing input, synchronize local + * type-parsing-affecting GUCs with the remote GUC value. + */ + applyRemoteGucs(&rgs); + /* synchronous query, use efficient tuple collection method */ materializeQueryResult(fcinfo, conn, conname, sql, fail); } *************** *** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) --- 805,817 ---- } else { + /* + * Before parsing input, synchronize local + * type-parsing-affecting GUCs with the remote GUC + * value. + */ + applyRemoteGucs(&rgs); + materializeResult(fcinfo, res); } } *************** *** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) --- 822,831 ---- /* if needed, close the connection to the database */ if (freeconn) PQfinish(conn); + + /* Pop any set GUCs, if necessary */ + restoreLocalGucs(&rgs); + PG_RE_THROW(); } PG_END_TRY(); *************** *** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async) --- 834,842 ---- if (freeconn) PQfinish(conn); + /* Pop any set GUCs, if necessary */ + restoreLocalGucs(&rgs); + return (Datum) 0; } *************** *** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option, --- 2967,3066 ---- return true; } + + /* Initializer for a "remoteGucs" struct value. */ + static void + initRemoteGucs(remoteGucs *rgs, PGconn *conn) + { + rgs->localGUCNestLevel = -1; + rgs->conn = conn; + } + + /* + * Scan a TupleDesc and, should it contain types that are sensitive to + * GUCs, acquire remote GUCs and set them in a new GUC nesting level. + * This is undone with restoreLocalGucs. + */ + static void + applyRemoteGucs(remoteGucs *rgs) + { + const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs; + + int i; + int addedGucNesting = false; + + /* + * Affected types require local GUC manipulations. Create a new + * GUC NestLevel to overlay the remote settings. + * + * Also, this nesting is done exactly once per remoteGucInfo + * structure, so expect it to come with an invalid NestLevel. + */ + Assert(rgs->localGUCNestLevel == -1); + + for (i = 0; i < numGucs; i += 1) + { + const char *gucName = parseAffectingGucs[i]; + const char *remoteVal = PQparameterStatus(rgs->conn, gucName); + const char *localVal; + int gucApplyStatus; + + /* + * Attempt to avoid GUC setting if the remote and local GUCs + * already have the same value. + * + * NB: Must error if the GUC is not found. + */ + localVal = GetConfigOption(gucName, false, true); + + if (remoteVal == NULL) + ereport(ERROR, + (errmsg("could not load parameter status of %s", + gucName))); + + /* + * An error must have been raised by now if GUC values could + * not be loaded for any reason. + */ + Assert(localVal != NULL); + Assert(remoteVal != NULL); + + if (strcmp(remoteVal, localVal) == 0) + continue; + + if (!addedGucNesting) + { + rgs->localGUCNestLevel = NewGUCNestLevel(); + addedGucNesting = true; + } + + gucApplyStatus = set_config_option(gucName, remoteVal, + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0); + if (gucApplyStatus != 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot load remote configuration %s " + "for type parsing", + gucName))); + } + } + + /* + * Restore local GUCs after they have been overlaid with remote + * settings for type parsing, destroying the GUC nesting level. + */ + static void + restoreLocalGucs(remoteGucs *rgs) + { + /* + * A new GUCNestLevel was not introduced, so don't bother + * restoring, either. + */ + if (rgs->localGUCNestLevel == -1) + { + return; + } + + AtEOXact_GUC(false, rgs->localGUCNestLevel); + } *** a/contrib/dblink/expected/dblink.out --- b/contrib/dblink/expected/dblink.out *************** *** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1, --- 913,1096 ---- DELETE FROM test_dropped WHERE id = '2' (1 row) + -- test the local mimicry of remote GUC values in parsing for affected + -- types + SET datestyle = ISO, MDY; + SET intervalstyle = postgres; + SET timezone = UTC; + SELECT dblink_connect('myconn','dbname=contrib_regression'); + dblink_connect + ---------------- + OK + (1 row) + + SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + dblink_exec + ------------- + SET + (1 row) + + -- The following attempt test various paths at which TupleDescs are + -- formed and inspected for containment of types requiring local GUC + -- setting. + -- single row synchronous case + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + a + ------------------------ + 2013-03-12 00:00:00+00 + (1 row) + + -- multi-row synchronous case + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + a + ------------------------ + 2013-03-12 00:00:00+00 + 2013-03-12 00:00:00+00 + (2 rows) + + -- single-row asynchronous case + SELECT * + FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t'); + dblink_send_query + ------------------- + 1 + (1 row) + + CREATE TEMPORARY TABLE result AS + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)); + SELECT * FROM result; + t + ------------------------ + 2013-03-12 00:00:00+00 + (1 row) + + DROP TABLE result; + -- multi-row asynchronous case + SELECT * + FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t'); + dblink_send_query + ------------------- + 1 + (1 row) + + CREATE TEMPORARY TABLE result AS + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)); + SELECT * FROM result; + t + ------------------------ + 2013-03-12 00:00:00+00 + 2013-03-12 00:00:00+00 + (2 rows) + + DROP TABLE result; + -- Try an ambiguous interval + SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;'); + dblink_exec + ------------- + SET + (1 row) + + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''-1 2:03:04'')) i') + AS i(i interval); + i + ------------------- + -1 days -02:03:04 + (1 row) + + -- Try swapping to another format to ensure the GUCs are tracked + -- properly through a change. + SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + dblink_exec + ------------- + SET + (1 row) + + CREATE TEMPORARY TABLE result (t timestamptz); + INSERT INTO result (SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz)); + SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;'); + dblink_exec + ------------- + SET + (1 row) + + INSERT INTO result (SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''03.12.2013 00:00:00+00'')) t') + AS t(a timestamptz)); + SELECT DISTINCT * FROM result; + t + ------------------------ + 2013-03-12 00:00:00+00 + (1 row) + + DROP TABLE result; + -- Check error throwing in dblink_fetch + SELECT dblink_open('myconn','error_cursor', + 'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);'); + dblink_open + ------------- + OK + (1 row) + + SELECT * + FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + i + --- + 1 + (1 row) + + SELECT * + FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + ERROR: invalid input syntax for integer: "not an int" + -- Make sure that the local values have retained their value in spite + -- of shenanigans on the connection. + SHOW datestyle; + DateStyle + ----------- + ISO, MDY + (1 row) + + SHOW intervalstyle; + IntervalStyle + --------------- + postgres + (1 row) + + -- Clean up GUC-setting tests + SELECT dblink_disconnect('myconn'); + dblink_disconnect + ------------------- + OK + (1 row) + + RESET datestyle; + RESET intervalstyle; + RESET timezone; *** a/contrib/dblink/sql/dblink.sql --- b/contrib/dblink/sql/dblink.sql *************** *** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1, --- 426,527 ---- SELECT dblink_build_sql_delete('test_dropped', '1', 1, ARRAY['2'::TEXT]); + + -- test the local mimicry of remote GUC values in parsing for affected + -- types + SET datestyle = ISO, MDY; + SET intervalstyle = postgres; + SET timezone = UTC; + SELECT dblink_connect('myconn','dbname=contrib_regression'); + SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + + -- The following attempt test various paths at which TupleDescs are + -- formed and inspected for containment of types requiring local GUC + -- setting. + + -- single row synchronous case + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + + -- multi-row synchronous case + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz); + + -- single-row asynchronous case + SELECT * + FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t'); + CREATE TEMPORARY TABLE result AS + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)); + SELECT * FROM result; + DROP TABLE result; + + -- multi-row asynchronous case + SELECT * + FROM dblink_send_query('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00''), + (''12.03.2013 00:00:00+00'')) t'); + CREATE TEMPORARY TABLE result AS + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)) + UNION ALL + (SELECT * from dblink_get_result('myconn') as t(t timestamptz)); + SELECT * FROM result; + DROP TABLE result; + + -- Try an ambiguous interval + SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;'); + SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''-1 2:03:04'')) i') + AS i(i interval); + + -- Try swapping to another format to ensure the GUCs are tracked + -- properly through a change. + SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;'); + CREATE TEMPORARY TABLE result (t timestamptz); + INSERT INTO result (SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''12.03.2013 00:00:00+00'')) t') + AS t(a timestamptz)); + SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;'); + INSERT INTO result (SELECT * + FROM dblink('myconn', + 'SELECT * FROM + (VALUES (''03.12.2013 00:00:00+00'')) t') + AS t(a timestamptz)); + SELECT DISTINCT * FROM result; + DROP TABLE result; + + -- Check error throwing in dblink_fetch + SELECT dblink_open('myconn','error_cursor', + 'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);'); + SELECT * + FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + SELECT * + FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int); + + -- Make sure that the local values have retained their value in spite + -- of shenanigans on the connection. + SHOW datestyle; + SHOW intervalstyle; + + -- Clean up GUC-setting tests + SELECT dblink_disconnect('myconn'); + RESET datestyle; + RESET intervalstyle; + RESET timezone;