#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"
*/
static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
static void prepTuplestoreResult(FunctionCallInfo fcinfo);
-static void materializeResult(FunctionCallInfo fcinfo, PGresult *res);
+static void materializeResult(FunctionCallInfo fcinfo, PGconn *conn,
+ PGresult *res);
static void materializeQueryResult(FunctionCallInfo fcinfo,
PGconn *conn,
const char *conname,
static void validate_pkattnums(Relation rel,
int2vector *pkattnums_arg, int32 pknumatts_arg,
int **pkattnums, int *pknumatts);
+static int applyRemoteGucs(PGconn *conn);
+static void restoreLocalGucs(int nestlevel);
/* Global */
static remoteConn *pconn = NULL;
errmsg("cursor \"%s\" does not exist", curname)));
}
- materializeResult(fcinfo, res);
+ materializeResult(fcinfo, conn, res);
return (Datum) 0;
}
}
else
{
- materializeResult(fcinfo, res);
+ materializeResult(fcinfo, conn, res);
}
}
}
* The PGresult will be released in this function.
*/
static void
-materializeResult(FunctionCallInfo fcinfo, PGresult *res)
+materializeResult(FunctionCallInfo fcinfo, PGconn *conn, PGresult *res)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
PG_TRY();
{
TupleDesc tupdesc;
- bool is_sql_cmd = false;
+ bool is_sql_cmd;
int ntuples;
int nfields;
if (ntuples > 0)
{
AttInMetadata *attinmeta;
+ int nestlevel = -1;
Tuplestorestate *tupstore;
MemoryContext oldcontext;
int row;
attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ /* Set GUCs to ensure we read GUC-sensitive data types correctly */
+ if (!is_sql_cmd)
+ nestlevel = applyRemoteGucs(conn);
+
oldcontext = MemoryContextSwitchTo(
rsinfo->econtext->ecxt_per_query_memory);
tupstore = tuplestore_begin_heap(true, false, work_mem);
tuplestore_puttuple(tupstore, tuple);
}
+ /* clean up GUC settings, if we changed any */
+ restoreLocalGucs(nestlevel);
+
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
}
storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql)
{
bool first = true;
+ int nestlevel = -1;
PGresult *res;
if (!PQsendQuery(conn, sql))
if (PQresultStatus(sinfo->cur_res) == PGRES_SINGLE_TUPLE)
{
/* got one row from possibly-bigger resultset */
+
+ /*
+ * Set GUCs to ensure we read GUC-sensitive data types correctly.
+ * We shouldn't do this until we have a row in hand, to ensure
+ * libpq has seen any earlier ParameterStatus protocol messages.
+ */
+ if (first && nestlevel < 0)
+ nestlevel = applyRemoteGucs(conn);
+
storeRow(sinfo, sinfo->cur_res, first);
PQclear(sinfo->cur_res);
}
}
+ /* clean up GUC settings, if we changed any */
+ restoreLocalGucs(nestlevel);
+
/* return last_res */
res = sinfo->last_res;
sinfo->last_res = NULL;
errmsg("invalid attribute number %d", pkattnum)));
}
}
+
+/*
+ * Copy the remote session's values of GUCs that affect datatype I/O
+ * and apply them locally in a new GUC nesting level. Returns the new
+ * nestlevel (which is needed by restoreLocalGucs to undo the settings),
+ * or -1 if no new nestlevel was needed.
+ *
+ * We use the equivalent of a function SET option to allow the settings to
+ * persist only until the caller calls restoreLocalGucs. If an error is
+ * thrown in between, guc.c will take care of undoing the settings.
+ */
+static int
+applyRemoteGucs(PGconn *conn)
+{
+ static const char *const GUCsAffectingIO[] = {
+ "DateStyle",
+ "IntervalStyle"
+ };
+
+ int nestlevel = -1;
+ int i;
+
+ for (i = 0; i < lengthof(GUCsAffectingIO); i++)
+ {
+ const char *gucName = GUCsAffectingIO[i];
+ const char *remoteVal = PQparameterStatus(conn, gucName);
+ const char *localVal;
+
+ /*
+ * If the remote server is pre-8.4, it won't have IntervalStyle, but
+ * that's okay because its output format won't be ambiguous. So just
+ * skip the GUC if we don't get a value for it. (We might eventually
+ * need more complicated logic with remote-version checks here.)
+ */
+ if (remoteVal == NULL)
+ continue;
+
+ /*
+ * Avoid GUC-setting overhead if the remote and local GUCs already
+ * have the same value.
+ */
+ localVal = GetConfigOption(gucName, false, false);
+ Assert(localVal != NULL);
+
+ if (strcmp(remoteVal, localVal) == 0)
+ continue;
+
+ /* Create new GUC nest level if we didn't already */
+ if (nestlevel < 0)
+ nestlevel = NewGUCNestLevel();
+
+ /* Apply the option (this will throw error on failure) */
+ (void) set_config_option(gucName, remoteVal,
+ PGC_USERSET, PGC_S_SESSION,
+ GUC_ACTION_SAVE, true, 0);
+ }
+
+ return nestlevel;
+}
+
+/*
+ * Restore local GUCs after they have been overlaid with remote settings.
+ */
+static void
+restoreLocalGucs(int nestlevel)
+{
+ /* Do nothing if no new nestlevel was created */
+ if (nestlevel > 0)
+ AtEOXact_GUC(true, nestlevel);
+}
DELETE FROM test_dropped WHERE id = '2'
(1 row)
+-- test local mimicry of remote GUC values that affect datatype I/O
+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)
+
+-- 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 * FROM result;
+ t
+------------------------
+ 2013-03-12 00:00:00+00
+ 2013-03-12 00:00:00+00
+(2 rows)
+
+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 settings have retained their values 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;
SELECT dblink_build_sql_delete('test_dropped', '1', 1,
ARRAY['2'::TEXT]);
+
+-- test local mimicry of remote GUC values that affect datatype I/O
+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;');
+
+-- 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 * 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 settings have retained their values 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;