static void storeRow(volatile storeInfo *sinfo, PGresult *res, bool first);
static remoteConn *getConnectionByName(const char *name);
static HTAB *createConnHash(void);
-static void createNewConnection(const char *name, remoteConn *rconn);
+static remoteConn *createNewConnection(const char *name);
static void deleteConnection(const char *name);
static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts);
static char **get_text_array_contents(ArrayType *array, int *numitems);
static char *generate_relation_name(Relation rel);
static void dblink_connstr_check(const char *connstr);
static bool dblink_connstr_has_pw(const char *connstr);
-static void dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr);
+static void dblink_security_check(PGconn *conn, const char *connname,
+ const char *connstr);
static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
bool fail, const char *fmt,...) pg_attribute_printf(5, 6);
static char *get_connect_string(const char *servername);
static uint32 dblink_we_get_result = 0;
/*
- * Following is list that holds multiple remote connections.
+ * Following is hash that holds multiple remote connections.
* Calling convention of each dblink function changes to accept
- * connection name as the first parameter. The connection list is
+ * connection name as the first parameter. The connection hash is
* much like ecpg e.g. a mapping between a name and a PGconn object.
+ *
+ * To avoid potentially leaking a PGconn object in case of out-of-memory
+ * errors, we first create the hash entry, then open the PGconn.
+ * Hence, a hash entry whose rconn.conn pointer is NULL must be
+ * understood as a leftover from a failed create; it should be ignored
+ * by lookup operations, and silently replaced by create operations.
*/
typedef struct remoteConnHashEnt
{
char name[NAMEDATALEN];
- remoteConn *rconn;
+ remoteConn rconn;
} remoteConnHashEnt;
/* initial number of connection hashes */
errmsg("could not establish connection"),
errdetail_internal("%s", msg)));
}
- dblink_security_check(conn, rconn, connstr);
+ dblink_security_check(conn, NULL, connstr);
if (PQclientEncoding(conn) != GetDatabaseEncoding())
PQsetClientEncoding(conn, GetDatabaseEncodingName());
freeconn = true;
else if (PG_NARGS() == 1)
conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
- if (connname)
- {
- rconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext,
- sizeof(remoteConn));
- rconn->conn = NULL;
- rconn->openCursorCount = 0;
- rconn->newXactForCursor = false;
- }
-
/* first check for valid foreign data server */
connstr = get_connect_string(conname_or_str);
if (connstr == NULL)
if (dblink_we_connect == 0)
dblink_we_connect = WaitEventExtensionNew("DblinkConnect");
+ /* if we need a hashtable entry, make that first, since it might fail */
+ if (connname)
+ {
+ rconn = createNewConnection(connname);
+ Assert(rconn->conn == NULL);
+ }
+
/* OK to make connection */
conn = libpqsrv_connect(connstr, dblink_we_connect);
{
msg = pchomp(PQerrorMessage(conn));
libpqsrv_disconnect(conn);
- if (rconn)
- pfree(rconn);
+ if (connname)
+ deleteConnection(connname);
ereport(ERROR,
(errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
}
/* check password actually used if not superuser */
- dblink_security_check(conn, rconn, connstr);
+ dblink_security_check(conn, connname, connstr);
/* attempt to set client encoding to match server encoding, if needed */
if (PQclientEncoding(conn) != GetDatabaseEncoding())
PQsetClientEncoding(conn, GetDatabaseEncodingName());
+ /* all OK, save away the conn */
if (connname)
{
rconn->conn = conn;
- createNewConnection(connname, rconn);
}
else
{
libpqsrv_disconnect(conn);
if (rconn)
- {
deleteConnection(conname);
- pfree(rconn);
- }
else
pconn->conn = NULL;
hash_seq_init(&status, remoteConnHash);
while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL)
{
+ /* ignore it if it's not an open connection */
+ if (hentry->rconn.conn == NULL)
+ continue;
/* stash away current value */
astate = accumArrayResult(astate,
CStringGetTextDatum(hentry->name),
hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
key, HASH_FIND, NULL);
- if (hentry)
- return hentry->rconn;
+ if (hentry && hentry->rconn.conn != NULL)
+ return &hentry->rconn;
return NULL;
}
HASH_ELEM | HASH_STRINGS);
}
-static void
-createNewConnection(const char *name, remoteConn *rconn)
+static remoteConn *
+createNewConnection(const char *name)
{
remoteConnHashEnt *hentry;
bool found;
hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key,
HASH_ENTER, &found);
- if (found)
- {
- libpqsrv_disconnect(rconn->conn);
- pfree(rconn);
-
+ if (found && hentry->rconn.conn != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("duplicate connection name")));
- }
- hentry->rconn = rconn;
+ /* New, or reusable, so initialize the rconn struct to zeroes */
+ memset(&hentry->rconn, 0, sizeof(remoteConn));
+
+ return &hentry->rconn;
}
static void
* We need to make sure that the connection made used credentials
* which were provided by the user, so check what credentials were
* used to connect and then make sure that they came from the user.
+ *
+ * On failure, we close "conn" and also delete the hashtable entry
+ * identified by "connname" (if that's not NULL).
*/
static void
-dblink_security_check(PGconn *conn, remoteConn *rconn, const char *connstr)
+dblink_security_check(PGconn *conn, const char *connname, const char *connstr)
{
/* Superuser bypasses security check */
if (superuser())
/* Otherwise, fail out */
libpqsrv_disconnect(conn);
- if (rconn)
- pfree(rconn);
+ if (connname)
+ deleteConnection(connname);
ereport(ERROR,
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),