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)
--- 629,643 ----
  				 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);
+ 	applyRemoteGucs(&rgs);
  	materializeResult(fcinfo, res);
+ 	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;
  
--- 688,699 ----
  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)
--- 762,777 ----
  		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)
--- 792,804 ----
  				}
  				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)
--- 809,815 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 818,826 ----
  	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,
--- 2951,3046 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Acquire remote GUCs that may affect type parsing and set them in a
+  * new GUC nesting level.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * 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,1095 ----
   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 tuples 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.
+ CREATE TEMPORARY TABLE result (t 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 dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.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,530 ----
  
  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 tuples 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.
+ CREATE TEMPORARY TABLE result (t 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 dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.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;
