summaryrefslogtreecommitdiff
path: root/contrib/dblink/dblink.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/dblink/dblink.c')
-rw-r--r--contrib/dblink/dblink.c453
1 files changed, 357 insertions, 96 deletions
diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 1e62d8091a..a81853fa91 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -9,7 +9,7 @@
* Shridhar Daithankar <shridhar_daithankar@persistent.co.in>
*
* contrib/dblink/dblink.c
- * Copyright (c) 2001-2012, PostgreSQL Global Development Group
+ * Copyright (c) 2001-2014, PostgreSQL Global Development Group
* ALL RIGHTS RESERVED;
*
* Permission to use, copy, modify, and distribute this software and its
@@ -35,18 +35,25 @@
#include <limits.h>
#include "libpq-fe.h"
-#include "funcapi.h"
+
+#include "access/htup_details.h"
+#include "access/reloptions.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/pg_foreign_server.h"
#include "catalog/pg_type.h"
+#include "catalog/pg_user_mapping.h"
#include "executor/spi.h"
#include "foreign/foreign.h"
+#include "funcapi.h"
+#include "lib/stringinfo.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "parser/scansup.h"
#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"
@@ -70,6 +77,9 @@ typedef struct storeInfo
AttInMetadata *attinmeta;
MemoryContext tmpcontext;
char **cstrs;
+ /* temp storage for results to avoid leaks on exception */
+ PGresult *last_res;
+ PGresult *cur_res;
} storeInfo;
/*
@@ -77,14 +87,15 @@ typedef struct storeInfo
*/
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,
const char *sql,
bool fail);
-static int storeHandler(PGresult *res, const PGdataValue *columns,
- const char **errmsgp, void *param);
+static PGresult *storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql);
+static void storeRow(storeInfo *sinfo, PGresult *res, bool first);
static remoteConn *getConnectionByName(const char *name);
static HTAB *createConnHash(void);
static void createNewConnection(const char *name, remoteConn *rconn);
@@ -107,6 +118,10 @@ static char *escape_param_str(const char *from);
static void validate_pkattnums(Relation rel,
int2vector *pkattnums_arg, int32 pknumatts_arg,
int **pkattnums, int *pknumatts);
+static bool is_valid_dblink_option(const PQconninfoOption *options,
+ const char *option, Oid context);
+static int applyRemoteGucs(PGconn *conn);
+static void restoreLocalGucs(int nestlevel);
/* Global */
static remoteConn *pconn = NULL;
@@ -194,7 +209,8 @@ typedef struct remoteConnHashEnt
errdetail_internal("%s", msg))); \
} \
dblink_security_check(conn, rconn); \
- PQsetClientEncoding(conn, GetDatabaseEncodingName()); \
+ if (PQclientEncoding(conn) != GetDatabaseEncoding()) \
+ PQsetClientEncoding(conn, GetDatabaseEncodingName()); \
freeconn = true; \
} \
} while (0)
@@ -273,8 +289,9 @@ dblink_connect(PG_FUNCTION_ARGS)
/* check password actually used if not superuser */
dblink_security_check(conn, rconn);
- /* attempt to set client encoding to match server encoding */
- PQsetClientEncoding(conn, GetDatabaseEncodingName());
+ /* attempt to set client encoding to match server encoding, if needed */
+ if (PQclientEncoding(conn) != GetDatabaseEncoding())
+ PQsetClientEncoding(conn, GetDatabaseEncodingName());
if (connname)
{
@@ -594,7 +611,7 @@ dblink_fetch(PG_FUNCTION_ARGS)
errmsg("cursor \"%s\" does not exist", curname)));
}
- materializeResult(fcinfo, res);
+ materializeResult(fcinfo, conn, res);
return (Datum) 0;
}
@@ -630,7 +647,7 @@ dblink_send_query(PG_FUNCTION_ARGS)
/* async query send */
retval = PQsendQuery(conn, sql);
if (retval != 1)
- elog(NOTICE, "%s", PQerrorMessage(conn));
+ elog(NOTICE, "could not send query: %s", PQerrorMessage(conn));
PG_RETURN_INT32(retval);
}
@@ -739,7 +756,7 @@ dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
}
else
{
- materializeResult(fcinfo, res);
+ materializeResult(fcinfo, conn, res);
}
}
}
@@ -795,7 +812,7 @@ prepTuplestoreResult(FunctionCallInfo fcinfo)
* 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;
@@ -805,7 +822,7 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
PG_TRY();
{
TupleDesc tupdesc;
- bool is_sql_cmd = false;
+ bool is_sql_cmd;
int ntuples;
int nfields;
@@ -866,6 +883,7 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
if (ntuples > 0)
{
AttInMetadata *attinmeta;
+ int nestlevel = -1;
Tuplestorestate *tupstore;
MemoryContext oldcontext;
int row;
@@ -873,6 +891,10 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
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);
@@ -909,6 +931,9 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
tuplestore_puttuple(tupstore, tuple);
}
+ /* clean up GUC settings, if we changed any */
+ restoreLocalGucs(nestlevel);
+
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
}
@@ -927,8 +952,10 @@ materializeResult(FunctionCallInfo fcinfo, PGresult *res)
/*
* Execute the given SQL command and store its results into a tuplestore
* to be returned as the result of the current function.
+ *
* This is equivalent to PQexec followed by materializeResult, but we make
- * use of libpq's "row processor" API to reduce per-row overhead.
+ * use of libpq's single-row mode to avoid accumulating the whole result
+ * inside libpq before it gets transferred to the tuplestore.
*/
static void
materializeQueryResult(FunctionCallInfo fcinfo,
@@ -944,19 +971,14 @@ materializeQueryResult(FunctionCallInfo fcinfo,
/* prepTuplestoreResult must have been called previously */
Assert(rsinfo->returnMode == SFRM_Materialize);
+ /* initialize storeInfo to empty */
+ memset(&sinfo, 0, sizeof(sinfo));
+ sinfo.fcinfo = fcinfo;
+
PG_TRY();
{
- /* initialize storeInfo to empty */
- memset(&sinfo, 0, sizeof(sinfo));
- sinfo.fcinfo = fcinfo;
-
- /* We'll collect tuples using storeHandler */
- PQsetRowProcessor(conn, storeHandler, &sinfo);
-
- res = PQexec(conn, sql);
-
- /* We don't keep the custom row processor installed permanently */
- PQsetRowProcessor(conn, NULL, NULL);
+ /* execute query, collecting any tuples into the tuplestore */
+ res = storeQueryResult(&sinfo, conn, sql);
if (!res ||
(PQresultStatus(res) != PGRES_COMMAND_OK &&
@@ -975,8 +997,8 @@ materializeQueryResult(FunctionCallInfo fcinfo,
else if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
/*
- * storeHandler didn't get called, so we need to convert the
- * command status string to a tuple manually
+ * storeRow didn't get called, so we need to convert the command
+ * status string to a tuple manually
*/
TupleDesc tupdesc;
AttInMetadata *attinmeta;
@@ -1008,25 +1030,30 @@ materializeQueryResult(FunctionCallInfo fcinfo,
tuplestore_puttuple(tupstore, tuple);
PQclear(res);
+ res = NULL;
}
else
{
Assert(PQresultStatus(res) == PGRES_TUPLES_OK);
- /* storeHandler should have created a tuplestore */
+ /* storeRow should have created a tuplestore */
Assert(rsinfo->setResult != NULL);
PQclear(res);
+ res = NULL;
}
+ PQclear(sinfo.last_res);
+ sinfo.last_res = NULL;
+ PQclear(sinfo.cur_res);
+ sinfo.cur_res = NULL;
}
PG_CATCH();
{
- /* be sure to unset the custom row processor */
- PQsetRowProcessor(conn, NULL, NULL);
/* be sure to release any libpq result we collected */
- if (res)
- PQclear(res);
+ PQclear(res);
+ PQclear(sinfo.last_res);
+ PQclear(sinfo.cur_res);
/* and clear out any pending data in libpq */
- while ((res = PQskipResult(conn)) != NULL)
+ while ((res = PQgetResult(conn)) != NULL)
PQclear(res);
PG_RE_THROW();
}
@@ -1034,23 +1061,85 @@ materializeQueryResult(FunctionCallInfo fcinfo,
}
/*
- * Custom row processor for materializeQueryResult.
- * Prototype of this function must match PQrowProcessor.
+ * Execute query, and send any result rows to sinfo->tuplestore.
*/
-static int
-storeHandler(PGresult *res, const PGdataValue *columns,
- const char **errmsgp, void *param)
+static PGresult *
+storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql)
+{
+ bool first = true;
+ int nestlevel = -1;
+ PGresult *res;
+
+ if (!PQsendQuery(conn, sql))
+ elog(ERROR, "could not send query: %s", PQerrorMessage(conn));
+
+ if (!PQsetSingleRowMode(conn)) /* shouldn't fail */
+ elog(ERROR, "failed to set single-row mode for dblink query");
+
+ for (;;)
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ sinfo->cur_res = PQgetResult(conn);
+ if (!sinfo->cur_res)
+ break;
+
+ 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);
+ sinfo->cur_res = NULL;
+ first = false;
+ }
+ else
+ {
+ /* if empty resultset, fill tuplestore header */
+ if (first && PQresultStatus(sinfo->cur_res) == PGRES_TUPLES_OK)
+ storeRow(sinfo, sinfo->cur_res, first);
+
+ /* store completed result at last_res */
+ PQclear(sinfo->last_res);
+ sinfo->last_res = sinfo->cur_res;
+ sinfo->cur_res = NULL;
+ first = true;
+ }
+ }
+
+ /* clean up GUC settings, if we changed any */
+ restoreLocalGucs(nestlevel);
+
+ /* return last_res */
+ res = sinfo->last_res;
+ sinfo->last_res = NULL;
+ return res;
+}
+
+/*
+ * Send single row to sinfo->tuplestore.
+ *
+ * If "first" is true, create the tuplestore using PGresult's metadata
+ * (in this case the PGresult might contain either zero or one row).
+ */
+static void
+storeRow(storeInfo *sinfo, PGresult *res, bool first)
{
- storeInfo *sinfo = (storeInfo *) param;
int nfields = PQnfields(res);
- char **cstrs = sinfo->cstrs;
HeapTuple tuple;
- char *pbuf;
- int pbuflen;
int i;
MemoryContext oldcontext;
- if (columns == NULL)
+ if (first)
{
/* Prepare for new result set */
ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo;
@@ -1098,13 +1187,16 @@ storeHandler(PGresult *res, const PGdataValue *columns,
sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* Create a new, empty tuplestore */
- oldcontext = MemoryContextSwitchTo(
- rsinfo->econtext->ecxt_per_query_memory);
+ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem);
rsinfo->setResult = sinfo->tuplestore;
rsinfo->setDesc = tupdesc;
MemoryContextSwitchTo(oldcontext);
+ /* Done if empty resultset */
+ if (PQntuples(res) == 0)
+ return;
+
/*
* Set up sufficiently-wide string pointers array; this won't change
* in size so it's easy to preallocate.
@@ -1121,11 +1213,10 @@ storeHandler(PGresult *res, const PGdataValue *columns,
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
-
- return 1;
}
- CHECK_FOR_INTERRUPTS();
+ /* Should have a single-row result if we get here */
+ Assert(PQntuples(res) == 1);
/*
* Do the following work in a temp context that we reset after each tuple.
@@ -1135,46 +1226,24 @@ storeHandler(PGresult *res, const PGdataValue *columns,
oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext);
/*
- * The strings passed to us are not null-terminated, but the datatype
- * input functions we're about to call require null termination. Copy the
- * strings and add null termination. As a micro-optimization, allocate
- * all the strings with one palloc.
+ * Fill cstrs with null-terminated strings of column values.
*/
- pbuflen = nfields; /* count the null terminators themselves */
for (i = 0; i < nfields; i++)
{
- int len = columns[i].len;
-
- if (len > 0)
- pbuflen += len;
- }
- pbuf = (char *) palloc(pbuflen);
-
- for (i = 0; i < nfields; i++)
- {
- int len = columns[i].len;
-
- if (len < 0)
- cstrs[i] = NULL;
+ if (PQgetisnull(res, 0, i))
+ sinfo->cstrs[i] = NULL;
else
- {
- cstrs[i] = pbuf;
- memcpy(pbuf, columns[i].value, len);
- pbuf += len;
- *pbuf++ = '\0';
- }
+ sinfo->cstrs[i] = PQgetvalue(res, 0, i);
}
/* Convert row to a tuple, and add it to the tuplestore */
- tuple = BuildTupleFromCStrings(sinfo->attinmeta, cstrs);
+ tuple = BuildTupleFromCStrings(sinfo->attinmeta, sinfo->cstrs);
tuplestore_puttuple(sinfo->tuplestore, tuple);
/* Clean up */
MemoryContextSwitchTo(oldcontext);
MemoryContextReset(sinfo->tmpcontext);
-
- return 1;
}
/*
@@ -1494,10 +1563,7 @@ dblink_get_pkey(PG_FUNCTION_ARGS)
Datum result;
values = (char **) palloc(2 * sizeof(char *));
- values[0] = (char *) palloc(12); /* sign, 10 digits, '\0' */
-
- sprintf(values[0], "%d", call_cntr + 1);
-
+ values[0] = psprintf("%d", call_cntr + 1);
values[1] = results[call_cntr];
/* build the tuple */
@@ -1875,6 +1941,75 @@ dblink_get_notify(PG_FUNCTION_ARGS)
return (Datum) 0;
}
+/*
+ * Validate the options given to a dblink foreign server or user mapping.
+ * Raise an error if any option is invalid.
+ *
+ * We just check the names of options here, so semantic errors in options,
+ * such as invalid numeric format, will be detected at the attempt to connect.
+ */
+PG_FUNCTION_INFO_V1(dblink_fdw_validator);
+Datum
+dblink_fdw_validator(PG_FUNCTION_ARGS)
+{
+ List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
+ Oid context = PG_GETARG_OID(1);
+ ListCell *cell;
+
+ static const PQconninfoOption *options = NULL;
+
+ /*
+ * Get list of valid libpq options.
+ *
+ * To avoid unnecessary work, we get the list once and use it throughout
+ * the lifetime of this backend process. We don't need to care about
+ * memory context issues, because PQconndefaults allocates with malloc.
+ */
+ if (!options)
+ {
+ options = PQconndefaults();
+ if (!options) /* assume reason for failure is OOM */
+ ereport(ERROR,
+ (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
+ errmsg("out of memory"),
+ errdetail("could not get libpq's default connection options")));
+ }
+
+ /* Validate each supplied option. */
+ foreach(cell, options_list)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+
+ if (!is_valid_dblink_option(options, def->defname, context))
+ {
+ /*
+ * Unknown option, or invalid option for the context specified, so
+ * complain about it. Provide a hint with list of valid options
+ * for the context.
+ */
+ StringInfoData buf;
+ const PQconninfoOption *opt;
+
+ initStringInfo(&buf);
+ for (opt = options; opt->keyword; opt++)
+ {
+ if (is_valid_dblink_option(options, opt->keyword, context))
+ appendStringInfo(&buf, "%s%s",
+ (buf.len > 0) ? ", " : "",
+ opt->keyword);
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_FDW_OPTION_NAME_NOT_FOUND),
+ errmsg("invalid option \"%s\"", def->defname),
+ errhint("Valid options in this context are: %s",
+ buf.data)));
+ }
+ }
+
+ PG_RETURN_VOID();
+}
+
+
/*************************************************************
* internal functions
*/
@@ -1910,7 +2045,7 @@ get_pkey_attnames(Relation rel, int16 *numatts)
ObjectIdGetDatum(RelationGetRelid(rel)));
scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
- SnapshotNow, 1, &skey);
+ NULL, 1, &skey);
while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
{
@@ -2033,14 +2168,14 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
continue;
if (needComma)
- appendStringInfo(&buf, ",");
+ appendStringInfoChar(&buf, ',');
appendStringInfoString(&buf,
quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
needComma = true;
}
- appendStringInfo(&buf, ") VALUES(");
+ appendStringInfoString(&buf, ") VALUES(");
/*
* Note: i is physical column number (counting from 0).
@@ -2052,7 +2187,7 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
continue;
if (needComma)
- appendStringInfo(&buf, ",");
+ appendStringInfoChar(&buf, ',');
key = get_attnum_pk_pos(pkattnums, pknumatts, i);
@@ -2067,10 +2202,10 @@ get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
pfree(val);
}
else
- appendStringInfo(&buf, "NULL");
+ appendStringInfoString(&buf, "NULL");
needComma = true;
}
- appendStringInfo(&buf, ")");
+ appendStringInfoChar(&buf, ')');
return (buf.data);
}
@@ -2096,7 +2231,7 @@ get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals
int pkattnum = pkattnums[i];
if (i > 0)
- appendStringInfo(&buf, " AND ");
+ appendStringInfoString(&buf, " AND ");
appendStringInfoString(&buf,
quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
@@ -2105,7 +2240,7 @@ get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals
appendStringInfo(&buf, " = %s",
quote_literal_cstr(tgt_pkattvals[i]));
else
- appendStringInfo(&buf, " IS NULL");
+ appendStringInfoString(&buf, " IS NULL");
}
return (buf.data);
@@ -2150,7 +2285,7 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
continue;
if (needComma)
- appendStringInfo(&buf, ", ");
+ appendStringInfoString(&buf, ", ");
appendStringInfo(&buf, "%s = ",
quote_ident_cstr(NameStr(tupdesc->attrs[i]->attname)));
@@ -2172,16 +2307,16 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
needComma = true;
}
- appendStringInfo(&buf, " WHERE ");
+ appendStringInfoString(&buf, " WHERE ");
for (i = 0; i < pknumatts; i++)
{
int pkattnum = pkattnums[i];
if (i > 0)
- appendStringInfo(&buf, " AND ");
+ appendStringInfoString(&buf, " AND ");
- appendStringInfo(&buf, "%s",
+ appendStringInfoString(&buf,
quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
val = tgt_pkattvals[i];
@@ -2189,7 +2324,7 @@ get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals
if (val != NULL)
appendStringInfo(&buf, " = %s", quote_literal_cstr(val));
else
- appendStringInfo(&buf, " IS NULL");
+ appendStringInfoString(&buf, " IS NULL");
}
return (buf.data);
@@ -2259,7 +2394,7 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
* Build sql statement to look up tuple of interest, ie, the one matching
* src_pkattvals. We used to use "SELECT *" here, but it's simpler to
* generate a result tuple that matches the table's physical structure,
- * with NULLs for any dropped columns. Otherwise we have to deal with two
+ * with NULLs for any dropped columns. Otherwise we have to deal with two
* different tupdescs and everything's very confusing.
*/
appendStringInfoString(&buf, "SELECT ");
@@ -2283,7 +2418,7 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
int pkattnum = pkattnums[i];
if (i > 0)
- appendStringInfo(&buf, " AND ");
+ appendStringInfoString(&buf, " AND ");
appendStringInfoString(&buf,
quote_ident_cstr(NameStr(tupdesc->attrs[pkattnum]->attname)));
@@ -2292,7 +2427,7 @@ get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pk
appendStringInfo(&buf, " = %s",
quote_literal_cstr(src_pkattvals[i]));
else
- appendStringInfo(&buf, " IS NULL");
+ appendStringInfoString(&buf, " IS NULL");
}
/*
@@ -2485,7 +2620,7 @@ dblink_security_check(PGconn *conn, remoteConn *rconn)
}
/*
- * For non-superusers, insist that the connstr specify a password. This
+ * For non-superusers, insist that the connstr specify a password. This
* prevents a password from being picked up from .pgpass, a service file,
* the environment, etc. We don't want the postgres user's passwords
* to be accessible to non-superusers.
@@ -2731,3 +2866,129 @@ validate_pkattnums(Relation rel,
errmsg("invalid attribute number %d", pkattnum)));
}
}
+
+/*
+ * Check if the specified connection option is valid.
+ *
+ * We basically allow whatever libpq thinks is an option, with these
+ * restrictions:
+ * debug options: disallowed
+ * "client_encoding": disallowed
+ * "user": valid only in USER MAPPING options
+ * secure options (eg password): valid only in USER MAPPING options
+ * others: valid only in FOREIGN SERVER options
+ *
+ * We disallow client_encoding because it would be overridden anyway via
+ * PQclientEncoding; allowing it to be specified would merely promote
+ * confusion.
+ */
+static bool
+is_valid_dblink_option(const PQconninfoOption *options, const char *option,
+ Oid context)
+{
+ const PQconninfoOption *opt;
+
+ /* Look up the option in libpq result */
+ for (opt = options; opt->keyword; opt++)
+ {
+ if (strcmp(opt->keyword, option) == 0)
+ break;
+ }
+ if (opt->keyword == NULL)
+ return false;
+
+ /* Disallow debug options (particularly "replication") */
+ if (strchr(opt->dispchar, 'D'))
+ return false;
+
+ /* Disallow "client_encoding" */
+ if (strcmp(opt->keyword, "client_encoding") == 0)
+ return false;
+
+ /*
+ * If the option is "user" or marked secure, it should be specified only
+ * in USER MAPPING. Others should be specified only in SERVER.
+ */
+ if (strcmp(opt->keyword, "user") == 0 || strchr(opt->dispchar, '*'))
+ {
+ if (context != UserMappingRelationId)
+ return false;
+ }
+ else
+ {
+ if (context != ForeignServerRelationId)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * 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);
+}