dblink-guc-sensitive-types-v2.patch
application/octet-stream
Filename: dblink-guc-sensitive-types-v2.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
Series: patch v2
| File | + | − |
|---|---|---|
| contrib/dblink/dblink.c | 136 | 0 |
| contrib/dblink/expected/dblink.out | 162 | 0 |
| contrib/dblink/sql/dblink.sql | 91 | 0 |
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.
Signed-off-by: Daniel Farina <daniel@fdr.io>
---
contrib/dblink/dblink.c | 139 ++++++++++++++++++++++++++++++-
contrib/dblink/expected/dblink.out | 162 ++++++++++++++++++++++++++++++++++++
contrib/dblink/sql/dblink.sql | 91 ++++++++++++++++++++
3 files changed, 389 insertions(+), 3 deletions(-)
*** 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,653 ----
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();
! {
! restoreLocalGucs(&rgs);
! }
! 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;
--- 698,709 ----
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)
--- 772,787 ----
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)
--- 802,814 ----
}
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)
--- 819,828 ----
/* 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)
--- 831,839 ----
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,
--- 2964,3033 ----
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)
+ {
+ int i;
+ const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+
+ /*
+ * 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);
+ rgs->localGUCNestLevel = NewGUCNestLevel();
+
+ for (i = 0; i < numGucs; i += 1)
+ {
+ const char *gucName = parseAffectingGucs[i];
+ const char *remoteVal = PQparameterStatus(rgs->conn, gucName);
+
+ int gucApplyStatus;
+
+ 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,1077 ----
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;
+ SELECT dblink_disconnect('myconn');
+ dblink_disconnect
+ -------------------
+ OK
+ (1 row)
+
+ -- 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)
+
+ 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,519 ----
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;
+
+ SELECT dblink_disconnect('myconn');
+
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;