Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * dblink.c
3 : : *
4 : : * Functions returning results from a remote database
5 : : *
6 : : * Joe Conway <mail@joeconway.com>
7 : : * And contributors:
8 : : * Darko Prenosil <Darko.Prenosil@finteh.hr>
9 : : * Shridhar Daithankar <shridhar_daithankar@persistent.co.in>
10 : : *
11 : : * contrib/dblink/dblink.c
12 : : * Copyright (c) 2001-2025, PostgreSQL Global Development Group
13 : : * ALL RIGHTS RESERVED;
14 : : *
15 : : * Permission to use, copy, modify, and distribute this software and its
16 : : * documentation for any purpose, without fee, and without a written agreement
17 : : * is hereby granted, provided that the above copyright notice and this
18 : : * paragraph and the following two paragraphs appear in all copies.
19 : : *
20 : : * IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
21 : : * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
22 : : * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
23 : : * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
24 : : * POSSIBILITY OF SUCH DAMAGE.
25 : : *
26 : : * THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
27 : : * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28 : : * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
29 : : * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
30 : : * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
31 : : *
32 : : */
33 : : #include "postgres.h"
34 : :
35 : : #include <limits.h>
36 : :
37 : : #include "access/htup_details.h"
38 : : #include "access/relation.h"
39 : : #include "access/reloptions.h"
40 : : #include "access/table.h"
41 : : #include "catalog/namespace.h"
42 : : #include "catalog/pg_foreign_data_wrapper.h"
43 : : #include "catalog/pg_foreign_server.h"
44 : : #include "catalog/pg_type.h"
45 : : #include "catalog/pg_user_mapping.h"
46 : : #include "commands/defrem.h"
47 : : #include "common/base64.h"
48 : : #include "executor/spi.h"
49 : : #include "foreign/foreign.h"
50 : : #include "funcapi.h"
51 : : #include "lib/stringinfo.h"
52 : : #include "libpq-fe.h"
53 : : #include "libpq/libpq-be.h"
54 : : #include "libpq/libpq-be-fe-helpers.h"
55 : : #include "mb/pg_wchar.h"
56 : : #include "miscadmin.h"
57 : : #include "parser/scansup.h"
58 : : #include "utils/acl.h"
59 : : #include "utils/builtins.h"
60 : : #include "utils/fmgroids.h"
61 : : #include "utils/guc.h"
62 : : #include "utils/lsyscache.h"
63 : : #include "utils/memutils.h"
64 : : #include "utils/rel.h"
65 : : #include "utils/varlena.h"
66 : : #include "utils/wait_event.h"
67 : :
164 tgl@sss.pgh.pa.us 68 :CBC 23 : PG_MODULE_MAGIC_EXT(
69 : : .name = "dblink",
70 : : .version = PG_VERSION
71 : : );
72 : :
73 : : typedef struct remoteConn
74 : : {
75 : : PGconn *conn; /* Hold the remote connection */
76 : : int openCursorCount; /* The number of open cursors */
77 : : bool newXactForCursor; /* Opened a transaction for a cursor */
78 : : } remoteConn;
79 : :
80 : : typedef struct storeInfo
81 : : {
82 : : FunctionCallInfo fcinfo;
83 : : Tuplestorestate *tuplestore;
84 : : AttInMetadata *attinmeta;
85 : : MemoryContext tmpcontext;
86 : : char **cstrs;
87 : : /* temp storage for results to avoid leaks on exception */
88 : : PGresult *last_res;
89 : : PGresult *cur_res;
90 : : } storeInfo;
91 : :
92 : : /*
93 : : * Internal declarations
94 : : */
95 : : static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
96 : : static void prepTuplestoreResult(FunctionCallInfo fcinfo);
97 : : static void materializeResult(FunctionCallInfo fcinfo, PGconn *conn,
98 : : PGresult *res);
99 : : static void materializeQueryResult(FunctionCallInfo fcinfo,
100 : : PGconn *conn,
101 : : const char *conname,
102 : : const char *sql,
103 : : bool fail);
104 : : static PGresult *storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql);
105 : : static void storeRow(storeInfo *sinfo, PGresult *res, bool first);
106 : : static remoteConn *getConnectionByName(const char *name);
107 : : static HTAB *createConnHash(void);
108 : : static remoteConn *createNewConnection(const char *name);
109 : : static void deleteConnection(const char *name);
110 : : static char **get_pkey_attnames(Relation rel, int16 *indnkeyatts);
111 : : static char **get_text_array_contents(ArrayType *array, int *numitems);
112 : : static char *get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
113 : : static char *get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals);
114 : : static char *get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals);
115 : : static char *quote_ident_cstr(char *rawstr);
116 : : static int get_attnum_pk_pos(int *pkattnums, int pknumatts, int key);
117 : : static HeapTuple get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals);
118 : : static Relation get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode);
119 : : static char *generate_relation_name(Relation rel);
120 : : static void dblink_connstr_check(const char *connstr);
121 : : static bool dblink_connstr_has_pw(const char *connstr);
122 : : static void dblink_security_check(PGconn *conn, const char *connname,
123 : : const char *connstr);
124 : : static void dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
125 : : bool fail, const char *fmt,...) pg_attribute_printf(5, 6);
126 : : static char *get_connect_string(const char *servername);
127 : : static char *escape_param_str(const char *str);
128 : : static void validate_pkattnums(Relation rel,
129 : : int2vector *pkattnums_arg, int32 pknumatts_arg,
130 : : int **pkattnums, int *pknumatts);
131 : : static bool is_valid_dblink_option(const PQconninfoOption *options,
132 : : const char *option, Oid context);
133 : : static int applyRemoteGucs(PGconn *conn);
134 : : static void restoreLocalGucs(int nestlevel);
135 : : static bool UseScramPassthrough(ForeignServer *foreign_server, UserMapping *user);
136 : : static void appendSCRAMKeysInfo(StringInfo buf);
137 : : static bool is_valid_dblink_fdw_option(const PQconninfoOption *options, const char *option,
138 : : Oid context);
139 : : static bool dblink_connstr_has_required_scram_options(const char *connstr);
140 : :
141 : : /* Global */
142 : : static remoteConn *pconn = NULL;
143 : : static HTAB *remoteConnHash = NULL;
144 : :
145 : : /* custom wait event values, retrieved from shared memory */
146 : : static uint32 dblink_we_connect = 0;
147 : : static uint32 dblink_we_get_conn = 0;
148 : : static uint32 dblink_we_get_result = 0;
149 : :
150 : : /*
151 : : * Following is hash that holds multiple remote connections.
152 : : * Calling convention of each dblink function changes to accept
153 : : * connection name as the first parameter. The connection hash is
154 : : * much like ecpg e.g. a mapping between a name and a PGconn object.
155 : : *
156 : : * To avoid potentially leaking a PGconn object in case of out-of-memory
157 : : * errors, we first create the hash entry, then open the PGconn.
158 : : * Hence, a hash entry whose rconn.conn pointer is NULL must be
159 : : * understood as a leftover from a failed create; it should be ignored
160 : : * by lookup operations, and silently replaced by create operations.
161 : : */
162 : :
163 : : typedef struct remoteConnHashEnt
164 : : {
165 : : char name[NAMEDATALEN];
166 : : remoteConn rconn;
167 : : } remoteConnHashEnt;
168 : :
169 : : /* initial number of connection hashes */
170 : : #define NUMCONN 16
171 : :
172 : : pg_noreturn static void
3177 peter_e@gmx.net 173 :UBC 0 : dblink_res_internalerror(PGconn *conn, PGresult *res, const char *p2)
174 : : {
175 : 0 : char *msg = pchomp(PQerrorMessage(conn));
176 : :
1161 peter@eisentraut.org 177 : 0 : PQclear(res);
3177 peter_e@gmx.net 178 [ # # ]: 0 : elog(ERROR, "%s: %s", p2, msg);
179 : : }
180 : :
181 : : pg_noreturn static void
3177 peter_e@gmx.net 182 :CBC 3 : dblink_conn_not_avail(const char *conname)
183 : : {
184 [ + + ]: 3 : if (conname)
185 [ + - ]: 1 : ereport(ERROR,
186 : : (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
187 : : errmsg("connection \"%s\" not available", conname)));
188 : : else
189 [ + - ]: 2 : ereport(ERROR,
190 : : (errcode(ERRCODE_CONNECTION_DOES_NOT_EXIST),
191 : : errmsg("connection not available")));
192 : : }
193 : :
194 : : static void
195 : 42 : dblink_get_conn(char *conname_or_str,
196 : : PGconn *volatile *conn_p, char **conname_p, volatile bool *freeconn_p)
197 : : {
198 : 42 : remoteConn *rconn = getConnectionByName(conname_or_str);
199 : : PGconn *conn;
200 : : char *conname;
201 : : bool freeconn;
202 : :
203 [ + + ]: 42 : if (rconn)
204 : : {
205 : 27 : conn = rconn->conn;
206 : 27 : conname = conname_or_str;
207 : 27 : freeconn = false;
208 : : }
209 : : else
210 : : {
211 : : const char *connstr;
212 : :
213 : 15 : connstr = get_connect_string(conname_or_str);
214 [ + + ]: 15 : if (connstr == NULL)
215 : 11 : connstr = conname_or_str;
216 : 15 : dblink_connstr_check(connstr);
217 : :
218 : : /* first time, allocate or get the custom wait event */
702 michael@paquier.xyz 219 [ + + ]: 12 : if (dblink_we_get_conn == 0)
220 : 8 : dblink_we_get_conn = WaitEventExtensionNew("DblinkGetConnect");
221 : :
222 : : /* OK to make connection */
223 : 12 : conn = libpqsrv_connect(connstr, dblink_we_get_conn);
224 : :
3177 peter_e@gmx.net 225 [ + + ]: 12 : if (PQstatus(conn) == CONNECTION_BAD)
226 : : {
227 : 3 : char *msg = pchomp(PQerrorMessage(conn));
228 : :
957 andres@anarazel.de 229 : 3 : libpqsrv_disconnect(conn);
3177 peter_e@gmx.net 230 [ + - ]: 3 : ereport(ERROR,
231 : : (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
232 : : errmsg("could not establish connection"),
233 : : errdetail_internal("%s", msg)));
234 : : }
235 : :
46 fujii@postgresql.org 236 :GNC 9 : PQsetNoticeReceiver(conn, libpqsrv_notice_receiver,
237 : : "received message via remote connection");
238 : :
100 tgl@sss.pgh.pa.us 239 :CBC 9 : dblink_security_check(conn, NULL, connstr);
3177 peter_e@gmx.net 240 [ - + ]: 8 : if (PQclientEncoding(conn) != GetDatabaseEncoding())
3177 peter_e@gmx.net 241 :UBC 0 : PQsetClientEncoding(conn, GetDatabaseEncodingName());
3177 peter_e@gmx.net 242 :CBC 8 : freeconn = true;
243 : 8 : conname = NULL;
244 : : }
245 : :
246 : 35 : *conn_p = conn;
247 : 35 : *conname_p = conname;
248 : 35 : *freeconn_p = freeconn;
249 : 35 : }
250 : :
251 : : static PGconn *
252 : 17 : dblink_get_named_conn(const char *conname)
253 : : {
254 : 17 : remoteConn *rconn = getConnectionByName(conname);
255 : :
256 [ + - ]: 17 : if (rconn)
257 : 17 : return rconn->conn;
258 : :
3099 peter_e@gmx.net 259 :UBC 0 : dblink_conn_not_avail(conname);
260 : : return NULL; /* keep compiler quiet */
261 : : }
262 : :
263 : : static void
3177 peter_e@gmx.net 264 :CBC 129 : dblink_init(void)
265 : : {
266 [ + + ]: 129 : if (!pconn)
267 : : {
607 noah@leadboat.com 268 [ + - ]: 12 : if (dblink_we_get_result == 0)
269 : 12 : dblink_we_get_result = WaitEventExtensionNew("DblinkGetResult");
270 : :
3177 peter_e@gmx.net 271 : 12 : pconn = (remoteConn *) MemoryContextAlloc(TopMemoryContext, sizeof(remoteConn));
272 : 12 : pconn->conn = NULL;
273 : 12 : pconn->openCursorCount = 0;
2943 274 : 12 : pconn->newXactForCursor = false;
275 : : }
3177 276 : 129 : }
277 : :
278 : : /*
279 : : * Create a persistent connection to another database
280 : : */
8405 bruce@momjian.us 281 : 17 : PG_FUNCTION_INFO_V1(dblink_connect);
282 : : Datum
283 : 16 : dblink_connect(PG_FUNCTION_ARGS)
284 : : {
5936 mail@joeconway.com 285 : 16 : char *conname_or_str = NULL;
8109 bruce@momjian.us 286 : 16 : char *connstr = NULL;
287 : 16 : char *connname = NULL;
288 : : char *msg;
289 : 16 : PGconn *conn = NULL;
7273 290 : 16 : remoteConn *rconn = NULL;
291 : :
3177 peter_e@gmx.net 292 : 16 : dblink_init();
293 : :
8069 bruce@momjian.us 294 [ + + ]: 16 : if (PG_NARGS() == 2)
295 : : {
5936 mail@joeconway.com 296 : 11 : conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(1));
6374 tgl@sss.pgh.pa.us 297 : 11 : connname = text_to_cstring(PG_GETARG_TEXT_PP(0));
298 : : }
8069 bruce@momjian.us 299 [ + - ]: 5 : else if (PG_NARGS() == 1)
5936 mail@joeconway.com 300 : 5 : conname_or_str = text_to_cstring(PG_GETARG_TEXT_PP(0));
301 : :
302 : : /* first check for valid foreign data server */
303 : 16 : connstr = get_connect_string(conname_or_str);
304 [ + + ]: 16 : if (connstr == NULL)
305 : 14 : connstr = conname_or_str;
306 : :
307 : : /* check password in connection string if not superuser */
6193 tgl@sss.pgh.pa.us 308 : 16 : dblink_connstr_check(connstr);
309 : :
310 : : /* first time, allocate or get the custom wait event */
702 michael@paquier.xyz 311 [ + + ]: 15 : if (dblink_we_connect == 0)
312 : 2 : dblink_we_connect = WaitEventExtensionNew("DblinkConnect");
313 : :
314 : : /* if we need a hashtable entry, make that first, since it might fail */
100 tgl@sss.pgh.pa.us 315 [ + + ]: 15 : if (connname)
316 : : {
317 : 10 : rconn = createNewConnection(connname);
318 [ - + ]: 9 : Assert(rconn->conn == NULL);
319 : : }
320 : :
321 : : /* OK to make connection */
702 michael@paquier.xyz 322 : 14 : conn = libpqsrv_connect(connstr, dblink_we_connect);
323 : :
8109 bruce@momjian.us 324 [ - + ]: 14 : if (PQstatus(conn) == CONNECTION_BAD)
325 : : {
3113 peter_e@gmx.net 326 :UBC 0 : msg = pchomp(PQerrorMessage(conn));
957 andres@anarazel.de 327 : 0 : libpqsrv_disconnect(conn);
100 tgl@sss.pgh.pa.us 328 [ # # ]: 0 : if (connname)
329 : 0 : deleteConnection(connname);
330 : :
8080 331 [ # # ]: 0 : ereport(ERROR,
332 : : (errcode(ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION),
333 : : errmsg("could not establish connection"),
334 : : errdetail_internal("%s", msg)));
335 : : }
336 : :
46 fujii@postgresql.org 337 :GNC 14 : PQsetNoticeReceiver(conn, libpqsrv_notice_receiver,
338 : : "received message via remote connection");
339 : :
340 : : /* check password actually used if not superuser */
100 tgl@sss.pgh.pa.us 341 :CBC 14 : dblink_security_check(conn, connname, connstr);
342 : :
343 : : /* attempt to set client encoding to match server encoding, if needed */
4291 mail@joeconway.com 344 [ - + ]: 14 : if (PQclientEncoding(conn) != GetDatabaseEncoding())
4291 mail@joeconway.com 345 :UBC 0 : PQsetClientEncoding(conn, GetDatabaseEncodingName());
346 : :
347 : : /* all OK, save away the conn */
8069 bruce@momjian.us 348 [ + + ]:CBC 14 : if (connname)
349 : : {
7273 350 : 9 : rconn->conn = conn;
351 : : }
352 : : else
353 : : {
3101 mail@joeconway.com 354 [ + + ]: 5 : if (pconn->conn)
949 andres@anarazel.de 355 : 1 : libpqsrv_disconnect(pconn->conn);
7263 mail@joeconway.com 356 : 5 : pconn->conn = conn;
357 : : }
358 : :
6374 tgl@sss.pgh.pa.us 359 : 14 : PG_RETURN_TEXT_P(cstring_to_text("OK"));
360 : : }
361 : :
362 : : /*
363 : : * Clear a persistent connection to another database
364 : : */
8405 bruce@momjian.us 365 : 10 : PG_FUNCTION_INFO_V1(dblink_disconnect);
366 : : Datum
367 : 13 : dblink_disconnect(PG_FUNCTION_ARGS)
368 : : {
8080 tgl@sss.pgh.pa.us 369 : 13 : char *conname = NULL;
7273 bruce@momjian.us 370 : 13 : remoteConn *rconn = NULL;
8109 371 : 13 : PGconn *conn = NULL;
372 : :
3177 peter_e@gmx.net 373 : 13 : dblink_init();
374 : :
8069 bruce@momjian.us 375 [ + + ]: 13 : if (PG_NARGS() == 1)
376 : : {
6374 tgl@sss.pgh.pa.us 377 : 9 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
7273 bruce@momjian.us 378 : 9 : rconn = getConnectionByName(conname);
379 [ + + ]: 9 : if (rconn)
380 : 8 : conn = rconn->conn;
381 : : }
382 : : else
7263 mail@joeconway.com 383 : 4 : conn = pconn->conn;
384 : :
8109 bruce@momjian.us 385 [ + + ]: 13 : if (!conn)
3177 peter_e@gmx.net 386 : 1 : dblink_conn_not_avail(conname);
387 : :
957 andres@anarazel.de 388 : 12 : libpqsrv_disconnect(conn);
7273 bruce@momjian.us 389 [ + + ]: 12 : if (rconn)
8080 tgl@sss.pgh.pa.us 390 : 8 : deleteConnection(conname);
391 : : else
7263 mail@joeconway.com 392 : 4 : pconn->conn = NULL;
393 : :
6374 tgl@sss.pgh.pa.us 394 : 12 : PG_RETURN_TEXT_P(cstring_to_text("OK"));
395 : : }
396 : :
397 : : /*
398 : : * opens a cursor using a persistent connection
399 : : */
8405 bruce@momjian.us 400 : 17 : PG_FUNCTION_INFO_V1(dblink_open);
401 : : Datum
402 : 9 : dblink_open(PG_FUNCTION_ARGS)
403 : : {
8403 404 : 9 : PGresult *res = NULL;
405 : : PGconn *conn;
8109 406 : 9 : char *curname = NULL;
407 : 9 : char *sql = NULL;
408 : 9 : char *conname = NULL;
409 : : StringInfoData buf;
7273 410 : 9 : remoteConn *rconn = NULL;
7853 mail@joeconway.com 411 : 9 : bool fail = true; /* default to backward compatible behavior */
412 : :
3177 peter_e@gmx.net 413 : 9 : dblink_init();
7129 neilc@samurai.com 414 : 9 : initStringInfo(&buf);
415 : :
8069 bruce@momjian.us 416 [ + + ]: 9 : if (PG_NARGS() == 2)
417 : : {
418 : : /* text,text */
6374 tgl@sss.pgh.pa.us 419 : 2 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
420 : 2 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
7263 mail@joeconway.com 421 : 2 : rconn = pconn;
422 : : }
8069 bruce@momjian.us 423 [ + + ]: 7 : else if (PG_NARGS() == 3)
424 : : {
425 : : /* might be text,text,text or text,text,bool */
7853 mail@joeconway.com 426 [ + + ]: 6 : if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID)
427 : : {
6374 tgl@sss.pgh.pa.us 428 : 1 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
429 : 1 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
7853 mail@joeconway.com 430 : 1 : fail = PG_GETARG_BOOL(2);
7263 431 : 1 : rconn = pconn;
432 : : }
433 : : else
434 : : {
6374 tgl@sss.pgh.pa.us 435 : 5 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
436 : 5 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
437 : 5 : sql = text_to_cstring(PG_GETARG_TEXT_PP(2));
7273 bruce@momjian.us 438 : 5 : rconn = getConnectionByName(conname);
439 : : }
440 : : }
7853 mail@joeconway.com 441 [ + - ]: 1 : else if (PG_NARGS() == 4)
442 : : {
443 : : /* text,text,text,bool */
6374 tgl@sss.pgh.pa.us 444 : 1 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
445 : 1 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
446 : 1 : sql = text_to_cstring(PG_GETARG_TEXT_PP(2));
7853 mail@joeconway.com 447 : 1 : fail = PG_GETARG_BOOL(3);
7273 bruce@momjian.us 448 : 1 : rconn = getConnectionByName(conname);
449 : : }
450 : :
7263 mail@joeconway.com 451 [ + - - + ]: 9 : if (!rconn || !rconn->conn)
3177 peter_e@gmx.net 452 :UBC 0 : dblink_conn_not_avail(conname);
453 : :
3076 peter_e@gmx.net 454 :CBC 9 : conn = rconn->conn;
455 : :
456 : : /* If we are not in a transaction, start one */
7263 mail@joeconway.com 457 [ + + ]: 9 : if (PQtransactionStatus(conn) == PQTRANS_IDLE)
458 : : {
607 noah@leadboat.com 459 : 7 : res = libpqsrv_exec(conn, "BEGIN", dblink_we_get_result);
7263 mail@joeconway.com 460 [ - + ]: 7 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3177 peter_e@gmx.net 461 :UBC 0 : dblink_res_internalerror(conn, res, "begin error");
7263 mail@joeconway.com 462 :CBC 7 : PQclear(res);
2943 peter_e@gmx.net 463 : 7 : rconn->newXactForCursor = true;
464 : :
465 : : /*
466 : : * Since transaction state was IDLE, we force cursor count to
467 : : * initially be 0. This is needed as a previous ABORT might have wiped
468 : : * out our transaction without maintaining the cursor count for us.
469 : : */
7017 mail@joeconway.com 470 : 7 : rconn->openCursorCount = 0;
471 : : }
472 : :
473 : : /* if we started a transaction, increment cursor count */
7263 474 [ + - ]: 9 : if (rconn->newXactForCursor)
475 : 9 : (rconn->openCursorCount)++;
476 : :
7129 neilc@samurai.com 477 : 9 : appendStringInfo(&buf, "DECLARE %s CURSOR FOR %s", curname, sql);
607 noah@leadboat.com 478 : 9 : res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
7853 mail@joeconway.com 479 [ + - + + ]: 9 : if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
480 : : {
2725 tgl@sss.pgh.pa.us 481 : 2 : dblink_res_error(conn, conname, res, fail,
482 : : "while opening cursor \"%s\"", curname);
6274 mail@joeconway.com 483 : 2 : PG_RETURN_TEXT_P(cstring_to_text("ERROR"));
484 : : }
485 : :
8109 bruce@momjian.us 486 : 7 : PQclear(res);
6374 tgl@sss.pgh.pa.us 487 : 7 : PG_RETURN_TEXT_P(cstring_to_text("OK"));
488 : : }
489 : :
490 : : /*
491 : : * closes a cursor
492 : : */
8405 bruce@momjian.us 493 : 14 : PG_FUNCTION_INFO_V1(dblink_close);
494 : : Datum
495 : 5 : dblink_close(PG_FUNCTION_ARGS)
496 : : {
497 : : PGconn *conn;
8403 498 : 5 : PGresult *res = NULL;
8109 499 : 5 : char *curname = NULL;
500 : 5 : char *conname = NULL;
501 : : StringInfoData buf;
7273 502 : 5 : remoteConn *rconn = NULL;
7853 mail@joeconway.com 503 : 5 : bool fail = true; /* default to backward compatible behavior */
504 : :
3177 peter_e@gmx.net 505 : 5 : dblink_init();
7129 neilc@samurai.com 506 : 5 : initStringInfo(&buf);
507 : :
8109 bruce@momjian.us 508 [ - + ]: 5 : if (PG_NARGS() == 1)
509 : : {
510 : : /* text */
6374 tgl@sss.pgh.pa.us 511 :UBC 0 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
7263 mail@joeconway.com 512 : 0 : rconn = pconn;
513 : : }
8069 bruce@momjian.us 514 [ + - ]:CBC 5 : else if (PG_NARGS() == 2)
515 : : {
516 : : /* might be text,text or text,bool */
7853 mail@joeconway.com 517 [ + + ]: 5 : if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID)
518 : : {
6374 tgl@sss.pgh.pa.us 519 : 2 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
7853 mail@joeconway.com 520 : 2 : fail = PG_GETARG_BOOL(1);
7263 521 : 2 : rconn = pconn;
522 : : }
523 : : else
524 : : {
6374 tgl@sss.pgh.pa.us 525 : 3 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
526 : 3 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
7273 bruce@momjian.us 527 : 3 : rconn = getConnectionByName(conname);
528 : : }
529 : : }
7853 mail@joeconway.com 530 [ - + ]: 5 : if (PG_NARGS() == 3)
531 : : {
532 : : /* text,text,bool */
6374 tgl@sss.pgh.pa.us 533 :UBC 0 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
534 : 0 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
7853 mail@joeconway.com 535 : 0 : fail = PG_GETARG_BOOL(2);
7273 bruce@momjian.us 536 : 0 : rconn = getConnectionByName(conname);
537 : : }
538 : :
7263 mail@joeconway.com 539 [ + - - + ]:CBC 5 : if (!rconn || !rconn->conn)
3177 peter_e@gmx.net 540 :UBC 0 : dblink_conn_not_avail(conname);
541 : :
3076 peter_e@gmx.net 542 :CBC 5 : conn = rconn->conn;
543 : :
7129 neilc@samurai.com 544 : 5 : appendStringInfo(&buf, "CLOSE %s", curname);
545 : :
546 : : /* close the cursor */
607 noah@leadboat.com 547 : 5 : res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
8403 bruce@momjian.us 548 [ + - + + ]: 5 : if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
549 : : {
2725 tgl@sss.pgh.pa.us 550 : 1 : dblink_res_error(conn, conname, res, fail,
551 : : "while closing cursor \"%s\"", curname);
6274 mail@joeconway.com 552 : 1 : PG_RETURN_TEXT_P(cstring_to_text("ERROR"));
553 : : }
554 : :
8405 bruce@momjian.us 555 : 4 : PQclear(res);
556 : :
557 : : /* if we started a transaction, decrement cursor count */
7263 mail@joeconway.com 558 [ + - ]: 4 : if (rconn->newXactForCursor)
559 : : {
560 : 4 : (rconn->openCursorCount)--;
561 : :
562 : : /* if count is zero, commit the transaction */
563 [ + + ]: 4 : if (rconn->openCursorCount == 0)
564 : : {
2943 peter_e@gmx.net 565 : 2 : rconn->newXactForCursor = false;
566 : :
607 noah@leadboat.com 567 : 2 : res = libpqsrv_exec(conn, "COMMIT", dblink_we_get_result);
7263 mail@joeconway.com 568 [ - + ]: 2 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3177 peter_e@gmx.net 569 :UBC 0 : dblink_res_internalerror(conn, res, "commit error");
7263 mail@joeconway.com 570 :CBC 2 : PQclear(res);
571 : : }
572 : : }
573 : :
6374 tgl@sss.pgh.pa.us 574 : 4 : PG_RETURN_TEXT_P(cstring_to_text("OK"));
575 : : }
576 : :
577 : : /*
578 : : * Fetch results from an open cursor
579 : : */
8405 bruce@momjian.us 580 : 17 : PG_FUNCTION_INFO_V1(dblink_fetch);
581 : : Datum
582 : 13 : dblink_fetch(PG_FUNCTION_ARGS)
583 : : {
5671 584 : 13 : PGresult *res = NULL;
585 : 13 : char *conname = NULL;
586 : 13 : remoteConn *rconn = NULL;
587 : 13 : PGconn *conn = NULL;
588 : : StringInfoData buf;
589 : 13 : char *curname = NULL;
590 : 13 : int howmany = 0;
591 : 13 : bool fail = true; /* default to backward compatible */
592 : :
4904 tgl@sss.pgh.pa.us 593 : 13 : prepTuplestoreResult(fcinfo);
594 : :
3177 peter_e@gmx.net 595 : 13 : dblink_init();
596 : :
5704 mail@joeconway.com 597 [ + + ]: 13 : if (PG_NARGS() == 4)
598 : : {
599 : : /* text,text,int,bool */
600 : 1 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
601 : 1 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
602 : 1 : howmany = PG_GETARG_INT32(2);
603 : 1 : fail = PG_GETARG_BOOL(3);
604 : :
605 : 1 : rconn = getConnectionByName(conname);
606 [ + - ]: 1 : if (rconn)
607 : 1 : conn = rconn->conn;
608 : : }
609 [ + + ]: 12 : else if (PG_NARGS() == 3)
610 : : {
611 : : /* text,text,int or text,int,bool */
612 [ + + ]: 8 : if (get_fn_expr_argtype(fcinfo->flinfo, 2) == BOOLOID)
613 : : {
614 : 2 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
615 : 2 : howmany = PG_GETARG_INT32(1);
616 : 2 : fail = PG_GETARG_BOOL(2);
617 : 2 : conn = pconn->conn;
618 : : }
619 : : else
620 : : {
6374 tgl@sss.pgh.pa.us 621 : 6 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
622 : 6 : curname = text_to_cstring(PG_GETARG_TEXT_PP(1));
8109 bruce@momjian.us 623 : 6 : howmany = PG_GETARG_INT32(2);
624 : :
7273 625 : 6 : rconn = getConnectionByName(conname);
626 [ + - ]: 6 : if (rconn)
627 : 6 : conn = rconn->conn;
628 : : }
629 : : }
5704 mail@joeconway.com 630 [ + - ]: 4 : else if (PG_NARGS() == 2)
631 : : {
632 : : /* text,int */
633 : 4 : curname = text_to_cstring(PG_GETARG_TEXT_PP(0));
634 : 4 : howmany = PG_GETARG_INT32(1);
635 : 4 : conn = pconn->conn;
636 : : }
637 : :
638 [ - + ]: 13 : if (!conn)
3177 peter_e@gmx.net 639 :UBC 0 : dblink_conn_not_avail(conname);
640 : :
5704 mail@joeconway.com 641 :CBC 13 : initStringInfo(&buf);
642 : 13 : appendStringInfo(&buf, "FETCH %d FROM %s", howmany, curname);
643 : :
644 : : /*
645 : : * Try to execute the query. Note that since libpq uses malloc, the
646 : : * PGresult will be long-lived even though we are still in a short-lived
647 : : * memory context.
648 : : */
607 noah@leadboat.com 649 : 13 : res = libpqsrv_exec(conn, buf.data, dblink_we_get_result);
5704 mail@joeconway.com 650 [ + - + - ]: 26 : if (!res ||
651 [ + + ]: 26 : (PQresultStatus(res) != PGRES_COMMAND_OK &&
652 : 13 : PQresultStatus(res) != PGRES_TUPLES_OK))
653 : : {
2725 tgl@sss.pgh.pa.us 654 : 5 : dblink_res_error(conn, conname, res, fail,
655 : : "while fetching from cursor \"%s\"", curname);
5704 mail@joeconway.com 656 : 3 : return (Datum) 0;
657 : : }
658 [ - + ]: 8 : else if (PQresultStatus(res) == PGRES_COMMAND_OK)
659 : : {
660 : : /* cursor does not exist - closed already or bad name */
8405 bruce@momjian.us 661 :UBC 0 : PQclear(res);
5704 mail@joeconway.com 662 [ # # ]: 0 : ereport(ERROR,
663 : : (errcode(ERRCODE_INVALID_CURSOR_NAME),
664 : : errmsg("cursor \"%s\" does not exist", curname)));
665 : : }
666 : :
4551 tgl@sss.pgh.pa.us 667 :CBC 8 : materializeResult(fcinfo, conn, res);
5704 mail@joeconway.com 668 : 7 : return (Datum) 0;
669 : : }
670 : :
671 : : /*
672 : : * Note: this is the new preferred version of dblink
673 : : */
8405 bruce@momjian.us 674 : 28 : PG_FUNCTION_INFO_V1(dblink_record);
675 : : Datum
676 : 34 : dblink_record(PG_FUNCTION_ARGS)
677 : : {
5940 mail@joeconway.com 678 : 34 : return dblink_record_internal(fcinfo, false);
679 : : }
680 : :
6944 681 : 5 : PG_FUNCTION_INFO_V1(dblink_send_query);
682 : : Datum
683 : 6 : dblink_send_query(PG_FUNCTION_ARGS)
684 : : {
685 : : PGconn *conn;
686 : : char *sql;
687 : : int retval;
688 : :
5940 689 [ + - ]: 6 : if (PG_NARGS() == 2)
690 : : {
3177 peter_e@gmx.net 691 : 6 : conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
5940 mail@joeconway.com 692 : 6 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
693 : : }
694 : : else
695 : : /* shouldn't happen */
5940 mail@joeconway.com 696 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments");
697 : :
698 : : /* async query send */
5940 mail@joeconway.com 699 :CBC 6 : retval = PQsendQuery(conn, sql);
700 [ - + ]: 6 : if (retval != 1)
3113 peter_e@gmx.net 701 [ # # ]:UBC 0 : elog(NOTICE, "could not send query: %s", pchomp(PQerrorMessage(conn)));
702 : :
5940 mail@joeconway.com 703 :CBC 6 : PG_RETURN_INT32(retval);
704 : : }
705 : :
6944 706 : 8 : PG_FUNCTION_INFO_V1(dblink_get_result);
707 : : Datum
708 : 8 : dblink_get_result(PG_FUNCTION_ARGS)
709 : : {
5940 710 : 8 : return dblink_record_internal(fcinfo, true);
711 : : }
712 : :
713 : : static Datum
714 : 42 : dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
715 : : {
4903 tgl@sss.pgh.pa.us 716 : 42 : PGconn *volatile conn = NULL;
717 : 42 : volatile bool freeconn = false;
718 : :
4904 719 : 42 : prepTuplestoreResult(fcinfo);
720 : :
3177 peter_e@gmx.net 721 : 42 : dblink_init();
722 : :
4903 tgl@sss.pgh.pa.us 723 [ + + ]: 42 : PG_TRY();
724 : : {
725 : 42 : char *sql = NULL;
726 : 42 : char *conname = NULL;
727 : 42 : bool fail = true; /* default to backward compatible */
728 : :
729 [ + + ]: 42 : if (!is_async)
730 : : {
731 [ + + ]: 34 : if (PG_NARGS() == 3)
732 : : {
733 : : /* text,text,bool */
3084 peter_e@gmx.net 734 : 1 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
4903 tgl@sss.pgh.pa.us 735 : 1 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
736 : 1 : fail = PG_GETARG_BOOL(2);
3084 peter_e@gmx.net 737 : 1 : dblink_get_conn(conname, &conn, &conname, &freeconn);
738 : : }
4903 tgl@sss.pgh.pa.us 739 [ + + ]: 33 : else if (PG_NARGS() == 2)
740 : : {
741 : : /* text,text or text,bool */
742 [ + + ]: 26 : if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID)
743 : : {
744 : 1 : sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
745 : 1 : fail = PG_GETARG_BOOL(1);
3084 peter_e@gmx.net 746 : 1 : conn = pconn->conn;
747 : : }
748 : : else
749 : : {
750 : 25 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
4903 tgl@sss.pgh.pa.us 751 : 25 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
3084 peter_e@gmx.net 752 : 25 : dblink_get_conn(conname, &conn, &conname, &freeconn);
753 : : }
754 : : }
4903 tgl@sss.pgh.pa.us 755 [ + - ]: 7 : else if (PG_NARGS() == 1)
756 : : {
757 : : /* text */
5704 mail@joeconway.com 758 : 7 : conn = pconn->conn;
759 : 7 : sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
760 : : }
761 : : else
762 : : /* shouldn't happen */
4903 tgl@sss.pgh.pa.us 763 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments");
764 : : }
765 : : else /* is_async */
766 : : {
767 : : /* get async result */
3084 peter_e@gmx.net 768 :CBC 8 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
769 : :
4903 tgl@sss.pgh.pa.us 770 [ - + ]: 8 : if (PG_NARGS() == 2)
771 : : {
772 : : /* text,bool */
4903 tgl@sss.pgh.pa.us 773 :UBC 0 : fail = PG_GETARG_BOOL(1);
3084 peter_e@gmx.net 774 : 0 : conn = dblink_get_named_conn(conname);
775 : : }
4903 tgl@sss.pgh.pa.us 776 [ + - ]:CBC 8 : else if (PG_NARGS() == 1)
777 : : {
778 : : /* text */
3084 peter_e@gmx.net 779 : 8 : conn = dblink_get_named_conn(conname);
780 : : }
781 : : else
782 : : /* shouldn't happen */
4903 tgl@sss.pgh.pa.us 783 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments");
784 : : }
785 : :
4903 tgl@sss.pgh.pa.us 786 [ + + ]:CBC 35 : if (!conn)
3177 peter_e@gmx.net 787 : 2 : dblink_conn_not_avail(conname);
788 : :
4903 tgl@sss.pgh.pa.us 789 [ + + ]: 33 : if (!is_async)
790 : : {
791 : : /* synchronous query, use efficient tuple collection method */
792 : 25 : materializeQueryResult(fcinfo, conn, conname, sql, fail);
793 : : }
794 : : else
795 : : {
796 : : /* async result retrieval, do it the old way */
607 noah@leadboat.com 797 : 8 : PGresult *res = libpqsrv_get_result(conn, dblink_we_get_result);
798 : :
799 : : /* NULL means we're all done with the async results */
4903 tgl@sss.pgh.pa.us 800 [ + + ]: 8 : if (res)
801 : : {
802 [ + - ]: 5 : if (PQresultStatus(res) != PGRES_COMMAND_OK &&
803 [ - + ]: 5 : PQresultStatus(res) != PGRES_TUPLES_OK)
804 : : {
2725 tgl@sss.pgh.pa.us 805 :UBC 0 : dblink_res_error(conn, conname, res, fail,
806 : : "while executing query");
807 : : /* if fail isn't set, we'll return an empty query result */
808 : : }
809 : : else
810 : : {
4551 tgl@sss.pgh.pa.us 811 :CBC 5 : materializeResult(fcinfo, conn, res);
812 : : }
813 : : }
814 : : }
815 : : }
2136 peter@eisentraut.org 816 : 9 : PG_FINALLY();
817 : : {
818 : : /* if needed, close the connection to the database */
4903 tgl@sss.pgh.pa.us 819 [ + + ]: 42 : if (freeconn)
957 andres@anarazel.de 820 : 7 : libpqsrv_disconnect(conn);
821 : : }
4903 tgl@sss.pgh.pa.us 822 [ + + ]: 42 : PG_END_TRY();
823 : :
5704 mail@joeconway.com 824 : 33 : return (Datum) 0;
825 : : }
826 : :
827 : : /*
828 : : * Verify function caller can handle a tuplestore result, and set up for that.
829 : : *
830 : : * Note: if the caller returns without actually creating a tuplestore, the
831 : : * executor will treat the function result as an empty set.
832 : : */
833 : : static void
4904 tgl@sss.pgh.pa.us 834 : 55 : prepTuplestoreResult(FunctionCallInfo fcinfo)
835 : : {
836 : 55 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
837 : :
838 : : /* check to see if query supports us returning a tuplestore */
839 [ + - - + ]: 55 : if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
4904 tgl@sss.pgh.pa.us 840 [ # # ]:UBC 0 : ereport(ERROR,
841 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
842 : : errmsg("set-valued function called in context that cannot accept a set")));
4904 tgl@sss.pgh.pa.us 843 [ - + ]:CBC 55 : if (!(rsinfo->allowedModes & SFRM_Materialize))
4904 tgl@sss.pgh.pa.us 844 [ # # ]:UBC 0 : ereport(ERROR,
845 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
846 : : errmsg("materialize mode required, but it is not allowed in this context")));
847 : :
848 : : /* let the executor know we're sending back a tuplestore */
4904 tgl@sss.pgh.pa.us 849 :CBC 55 : rsinfo->returnMode = SFRM_Materialize;
850 : :
851 : : /* caller must fill these to return a non-empty result */
852 : 55 : rsinfo->setResult = NULL;
853 : 55 : rsinfo->setDesc = NULL;
854 : 55 : }
855 : :
856 : : /*
857 : : * Copy the contents of the PGresult into a tuplestore to be returned
858 : : * as the result of the current function.
859 : : * The PGresult will be released in this function.
860 : : */
861 : : static void
4551 862 : 13 : materializeResult(FunctionCallInfo fcinfo, PGconn *conn, PGresult *res)
863 : : {
5671 bruce@momjian.us 864 : 13 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
865 : : TupleDesc tupdesc;
866 : : bool is_sql_cmd;
867 : : int ntuples;
868 : : int nfields;
869 : :
870 : : /* prepTuplestoreResult must have been called previously */
5704 mail@joeconway.com 871 [ - + ]: 13 : Assert(rsinfo->returnMode == SFRM_Materialize);
872 : :
43 tgl@sss.pgh.pa.us 873 [ - + ]:GNC 13 : if (PQresultStatus(res) == PGRES_COMMAND_OK)
874 : : {
43 tgl@sss.pgh.pa.us 875 :UNC 0 : is_sql_cmd = true;
876 : :
877 : : /*
878 : : * need a tuple descriptor representing one TEXT column to return the
879 : : * command status string as our result tuple
880 : : */
881 : 0 : tupdesc = CreateTemplateTupleDesc(1);
882 : 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
883 : : TEXTOID, -1, 0);
884 : 0 : ntuples = 1;
885 : 0 : nfields = 1;
886 : : }
887 : : else
888 : : {
43 tgl@sss.pgh.pa.us 889 [ - + ]:GNC 13 : Assert(PQresultStatus(res) == PGRES_TUPLES_OK);
890 : :
891 : 13 : is_sql_cmd = false;
892 : :
893 : : /* get a tuple descriptor for our result type */
894 [ + - - ]: 13 : switch (get_call_result_type(fcinfo, NULL, &tupdesc))
895 : : {
896 : 13 : case TYPEFUNC_COMPOSITE:
897 : : /* success */
898 : 13 : break;
43 tgl@sss.pgh.pa.us 899 :UNC 0 : case TYPEFUNC_RECORD:
900 : : /* failed to determine actual type of RECORD */
901 [ # # ]: 0 : ereport(ERROR,
902 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
903 : : errmsg("function returning record called in context "
904 : : "that cannot accept type record")));
905 : : break;
906 : 0 : default:
907 : : /* result type isn't composite */
908 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
909 : : break;
910 : : }
911 : :
912 : : /* make sure we have a persistent copy of the tupdesc */
43 tgl@sss.pgh.pa.us 913 :GNC 13 : tupdesc = CreateTupleDescCopy(tupdesc);
914 : 13 : ntuples = PQntuples(res);
915 : 13 : nfields = PQnfields(res);
916 : : }
917 : :
918 : : /*
919 : : * check result and tuple descriptor have the same number of columns
920 : : */
921 [ - + ]: 13 : if (nfields != tupdesc->natts)
43 tgl@sss.pgh.pa.us 922 [ # # ]:UNC 0 : ereport(ERROR,
923 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
924 : : errmsg("remote query result rowtype does not match "
925 : : "the specified FROM clause rowtype")));
926 : :
43 tgl@sss.pgh.pa.us 927 [ + - ]:GNC 13 : if (ntuples > 0)
928 : : {
929 : : AttInMetadata *attinmeta;
930 : 13 : int nestlevel = -1;
931 : : Tuplestorestate *tupstore;
932 : : MemoryContext oldcontext;
933 : : int row;
934 : : char **values;
935 : :
936 : 13 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
937 : :
938 : : /* Set GUCs to ensure we read GUC-sensitive data types correctly */
939 [ + - ]: 13 : if (!is_sql_cmd)
940 : 13 : nestlevel = applyRemoteGucs(conn);
941 : :
942 : 13 : oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
943 : 13 : tupstore = tuplestore_begin_heap(true, false, work_mem);
944 : 13 : rsinfo->setResult = tupstore;
945 : 13 : rsinfo->setDesc = tupdesc;
946 : 13 : MemoryContextSwitchTo(oldcontext);
947 : :
948 : 13 : values = palloc_array(char *, nfields);
949 : :
950 : : /* put all tuples into the tuplestore */
951 [ + + ]: 49 : for (row = 0; row < ntuples; row++)
952 : : {
953 : : HeapTuple tuple;
954 : :
955 [ + - ]: 37 : if (!is_sql_cmd)
956 : : {
957 : : int i;
958 : :
959 [ + + ]: 138 : for (i = 0; i < nfields; i++)
960 : : {
961 [ - + ]: 101 : if (PQgetisnull(res, row, i))
43 tgl@sss.pgh.pa.us 962 :UNC 0 : values[i] = NULL;
963 : : else
43 tgl@sss.pgh.pa.us 964 :GNC 101 : values[i] = PQgetvalue(res, row, i);
965 : : }
966 : : }
967 : : else
968 : : {
43 tgl@sss.pgh.pa.us 969 :UNC 0 : values[0] = PQcmdStatus(res);
970 : : }
971 : :
972 : : /* build the tuple and put it into the tuplestore. */
43 tgl@sss.pgh.pa.us 973 :GNC 37 : tuple = BuildTupleFromCStrings(attinmeta, values);
974 : 36 : tuplestore_puttuple(tupstore, tuple);
975 : : }
976 : :
977 : : /* clean up GUC settings, if we changed any */
978 : 12 : restoreLocalGucs(nestlevel);
979 : : }
980 : :
981 : 12 : PQclear(res);
8405 bruce@momjian.us 982 :CBC 12 : }
983 : :
984 : : /*
985 : : * Execute the given SQL command and store its results into a tuplestore
986 : : * to be returned as the result of the current function.
987 : : *
988 : : * This is equivalent to PQexec followed by materializeResult, but we make
989 : : * use of libpq's single-row mode to avoid accumulating the whole result
990 : : * inside libpq before it gets transferred to the tuplestore.
991 : : */
992 : : static void
4903 tgl@sss.pgh.pa.us 993 : 25 : materializeQueryResult(FunctionCallInfo fcinfo,
994 : : PGconn *conn,
995 : : const char *conname,
996 : : const char *sql,
997 : : bool fail)
998 : : {
999 : 25 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1000 : :
1001 : : /* prepTuplestoreResult must have been called previously */
1002 [ - + ]: 25 : Assert(rsinfo->returnMode == SFRM_Materialize);
1003 : :
1004 : : /* Use a PG_TRY block to ensure we pump libpq dry of results */
1005 [ + - ]: 25 : PG_TRY();
1006 : : {
43 tgl@sss.pgh.pa.us 1007 :GNC 25 : storeInfo sinfo = {0};
1008 : : PGresult *res;
1009 : :
1010 : 25 : sinfo.fcinfo = fcinfo;
1011 : : /* Create short-lived memory context for data conversions */
4096 mail@joeconway.com 1012 :CBC 25 : sinfo.tmpcontext = AllocSetContextCreate(CurrentMemoryContext,
1013 : : "dblink temporary context",
1014 : : ALLOCSET_DEFAULT_SIZES);
1015 : :
1016 : : /* execute query, collecting any tuples into the tuplestore */
4783 tgl@sss.pgh.pa.us 1017 : 25 : res = storeQueryResult(&sinfo, conn, sql);
1018 : :
4903 1019 [ + - ]: 25 : if (!res ||
1020 [ + - ]: 25 : (PQresultStatus(res) != PGRES_COMMAND_OK &&
1021 [ + + ]: 25 : PQresultStatus(res) != PGRES_TUPLES_OK))
4903 tgl@sss.pgh.pa.us 1022 :ECB (2) : {
43 tgl@sss.pgh.pa.us 1023 :GNC 2 : dblink_res_error(conn, conname, res, fail,
1024 : : "while executing query");
1025 : : /* if fail isn't set, we'll return an empty query result */
1026 : : }
4903 tgl@sss.pgh.pa.us 1027 [ - + ]:CBC 23 : else if (PQresultStatus(res) == PGRES_COMMAND_OK)
1028 : : {
1029 : : /*
1030 : : * storeRow didn't get called, so we need to convert the command
1031 : : * status string to a tuple manually
1032 : : */
1033 : : TupleDesc tupdesc;
1034 : : AttInMetadata *attinmeta;
1035 : : Tuplestorestate *tupstore;
1036 : : HeapTuple tuple;
1037 : : char *values[1];
1038 : : MemoryContext oldcontext;
1039 : :
1040 : : /*
1041 : : * need a tuple descriptor representing one TEXT column to return
1042 : : * the command status string as our result tuple
1043 : : */
2482 andres@anarazel.de 1044 :UBC 0 : tupdesc = CreateTemplateTupleDesc(1);
4903 tgl@sss.pgh.pa.us 1045 : 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "status",
1046 : : TEXTOID, -1, 0);
1047 : 0 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
1048 : :
2046 alvherre@alvh.no-ip. 1049 : 0 : oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
4903 tgl@sss.pgh.pa.us 1050 : 0 : tupstore = tuplestore_begin_heap(true, false, work_mem);
1051 : 0 : rsinfo->setResult = tupstore;
1052 : 0 : rsinfo->setDesc = tupdesc;
1053 : 0 : MemoryContextSwitchTo(oldcontext);
1054 : :
1055 : 0 : values[0] = PQcmdStatus(res);
1056 : :
1057 : : /* build the tuple and put it into the tuplestore. */
1058 : 0 : tuple = BuildTupleFromCStrings(attinmeta, values);
1059 : 0 : tuplestore_puttuple(tupstore, tuple);
1060 : :
1061 : 0 : PQclear(res);
1062 : : }
1063 : : else
1064 : : {
4903 tgl@sss.pgh.pa.us 1065 [ - + ]:CBC 23 : Assert(PQresultStatus(res) == PGRES_TUPLES_OK);
1066 : : /* storeRow should have created a tuplestore */
1067 [ - + ]: 23 : Assert(rsinfo->setResult != NULL);
1068 : :
1069 : 23 : PQclear(res);
1070 : : }
1071 : :
1072 : : /* clean up data conversion short-lived memory context */
4096 mail@joeconway.com 1073 [ + - ]: 25 : if (sinfo.tmpcontext != NULL)
1074 : 25 : MemoryContextDelete(sinfo.tmpcontext);
1075 : :
4783 tgl@sss.pgh.pa.us 1076 : 25 : PQclear(sinfo.last_res);
1077 : 25 : PQclear(sinfo.cur_res);
1078 : : }
4903 tgl@sss.pgh.pa.us 1079 :UBC 0 : PG_CATCH();
1080 : : {
1081 : : PGresult *res;
1082 : :
1083 : : /* be sure to clear out any pending data in libpq */
607 noah@leadboat.com 1084 [ # # ]: 0 : while ((res = libpqsrv_get_result(conn, dblink_we_get_result)) !=
1085 : : NULL)
4903 tgl@sss.pgh.pa.us 1086 : 0 : PQclear(res);
1087 : 0 : PG_RE_THROW();
1088 : : }
4903 tgl@sss.pgh.pa.us 1089 [ - + ]:CBC 25 : PG_END_TRY();
1090 : 25 : }
1091 : :
1092 : : /*
1093 : : * Execute query, and send any result rows to sinfo->tuplestore.
1094 : : */
1095 : : static PGresult *
43 tgl@sss.pgh.pa.us 1096 :GNC 25 : storeQueryResult(storeInfo *sinfo, PGconn *conn, const char *sql)
1097 : : {
4783 tgl@sss.pgh.pa.us 1098 :CBC 25 : bool first = true;
4551 1099 : 25 : int nestlevel = -1;
1100 : : PGresult *res;
1101 : :
4783 1102 [ - + ]: 25 : if (!PQsendQuery(conn, sql))
3113 peter_e@gmx.net 1103 [ # # ]:UBC 0 : elog(ERROR, "could not send query: %s", pchomp(PQerrorMessage(conn)));
1104 : :
2999 tgl@sss.pgh.pa.us 1105 [ - + ]:CBC 25 : if (!PQsetSingleRowMode(conn)) /* shouldn't fail */
4783 tgl@sss.pgh.pa.us 1106 [ # # ]:UBC 0 : elog(ERROR, "failed to set single-row mode for dblink query");
1107 : :
1108 : : for (;;)
1109 : : {
4783 tgl@sss.pgh.pa.us 1110 [ - + ]:CBC 204 : CHECK_FOR_INTERRUPTS();
1111 : :
607 noah@leadboat.com 1112 : 204 : sinfo->cur_res = libpqsrv_get_result(conn, dblink_we_get_result);
4783 tgl@sss.pgh.pa.us 1113 [ + + ]: 204 : if (!sinfo->cur_res)
1114 : 25 : break;
1115 : :
1116 [ + + ]: 179 : if (PQresultStatus(sinfo->cur_res) == PGRES_SINGLE_TUPLE)
1117 : : {
1118 : : /* got one row from possibly-bigger resultset */
1119 : :
1120 : : /*
1121 : : * Set GUCs to ensure we read GUC-sensitive data types correctly.
1122 : : * We shouldn't do this until we have a row in hand, to ensure
1123 : : * libpq has seen any earlier ParameterStatus protocol messages.
1124 : : */
4551 1125 [ + + + - ]: 154 : if (first && nestlevel < 0)
1126 : 23 : nestlevel = applyRemoteGucs(conn);
1127 : :
4783 1128 : 154 : storeRow(sinfo, sinfo->cur_res, first);
1129 : :
1130 : 154 : PQclear(sinfo->cur_res);
1131 : 154 : sinfo->cur_res = NULL;
1132 : 154 : first = false;
1133 : : }
1134 : : else
1135 : : {
1136 : : /* if empty resultset, fill tuplestore header */
1137 [ + + - + ]: 25 : if (first && PQresultStatus(sinfo->cur_res) == PGRES_TUPLES_OK)
4783 tgl@sss.pgh.pa.us 1138 :UBC 0 : storeRow(sinfo, sinfo->cur_res, first);
1139 : :
1140 : : /* store completed result at last_res */
4783 tgl@sss.pgh.pa.us 1141 :CBC 25 : PQclear(sinfo->last_res);
1142 : 25 : sinfo->last_res = sinfo->cur_res;
1143 : 25 : sinfo->cur_res = NULL;
1144 : 25 : first = true;
1145 : : }
1146 : : }
1147 : :
1148 : : /* clean up GUC settings, if we changed any */
4551 1149 : 25 : restoreLocalGucs(nestlevel);
1150 : :
1151 : : /* return last_res */
4783 1152 : 25 : res = sinfo->last_res;
1153 : 25 : sinfo->last_res = NULL;
1154 : 25 : return res;
1155 : : }
1156 : :
1157 : : /*
1158 : : * Send single row to sinfo->tuplestore.
1159 : : *
1160 : : * If "first" is true, create the tuplestore using PGresult's metadata
1161 : : * (in this case the PGresult might contain either zero or one row).
1162 : : */
1163 : : static void
43 tgl@sss.pgh.pa.us 1164 :GNC 154 : storeRow(storeInfo *sinfo, PGresult *res, bool first)
1165 : : {
4903 tgl@sss.pgh.pa.us 1166 :CBC 154 : int nfields = PQnfields(res);
1167 : : HeapTuple tuple;
1168 : : int i;
1169 : : MemoryContext oldcontext;
1170 : :
4783 1171 [ + + ]: 154 : if (first)
1172 : : {
1173 : : /* Prepare for new result set */
4903 1174 : 23 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) sinfo->fcinfo->resultinfo;
1175 : : TupleDesc tupdesc;
1176 : :
1177 : : /*
1178 : : * It's possible to get more than one result set if the query string
1179 : : * contained multiple SQL commands. In that case, we follow PQexec's
1180 : : * traditional behavior of throwing away all but the last result.
1181 : : */
1182 [ - + ]: 23 : if (sinfo->tuplestore)
4903 tgl@sss.pgh.pa.us 1183 :UBC 0 : tuplestore_end(sinfo->tuplestore);
4903 tgl@sss.pgh.pa.us 1184 :CBC 23 : sinfo->tuplestore = NULL;
1185 : :
1186 : : /* get a tuple descriptor for our result type */
1187 [ + - - ]: 23 : switch (get_call_result_type(sinfo->fcinfo, NULL, &tupdesc))
1188 : : {
1189 : 23 : case TYPEFUNC_COMPOSITE:
1190 : : /* success */
1191 : 23 : break;
4903 tgl@sss.pgh.pa.us 1192 :UBC 0 : case TYPEFUNC_RECORD:
1193 : : /* failed to determine actual type of RECORD */
1194 [ # # ]: 0 : ereport(ERROR,
1195 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1196 : : errmsg("function returning record called in context "
1197 : : "that cannot accept type record")));
1198 : : break;
1199 : 0 : default:
1200 : : /* result type isn't composite */
1201 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
1202 : : break;
1203 : : }
1204 : :
1205 : : /* make sure we have a persistent copy of the tupdesc */
4903 tgl@sss.pgh.pa.us 1206 :CBC 23 : tupdesc = CreateTupleDescCopy(tupdesc);
1207 : :
1208 : : /* check result and tuple descriptor have the same number of columns */
1209 [ - + ]: 23 : if (nfields != tupdesc->natts)
4903 tgl@sss.pgh.pa.us 1210 [ # # ]:UBC 0 : ereport(ERROR,
1211 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
1212 : : errmsg("remote query result rowtype does not match "
1213 : : "the specified FROM clause rowtype")));
1214 : :
1215 : : /* Prepare attinmeta for later data conversions */
4903 tgl@sss.pgh.pa.us 1216 :CBC 23 : sinfo->attinmeta = TupleDescGetAttInMetadata(tupdesc);
1217 : :
1218 : : /* Create a new, empty tuplestore */
4783 1219 : 23 : oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
4903 1220 : 23 : sinfo->tuplestore = tuplestore_begin_heap(true, false, work_mem);
1221 : 23 : rsinfo->setResult = sinfo->tuplestore;
1222 : 23 : rsinfo->setDesc = tupdesc;
1223 : 23 : MemoryContextSwitchTo(oldcontext);
1224 : :
1225 : : /* Done if empty resultset */
4783 1226 [ - + ]: 23 : if (PQntuples(res) == 0)
4783 tgl@sss.pgh.pa.us 1227 :UBC 0 : return;
1228 : :
1229 : : /*
1230 : : * Set up sufficiently-wide string pointers array; this won't change
1231 : : * in size so it's easy to preallocate.
1232 : : */
4903 tgl@sss.pgh.pa.us 1233 [ - + ]:CBC 23 : if (sinfo->cstrs)
4903 tgl@sss.pgh.pa.us 1234 :UBC 0 : pfree(sinfo->cstrs);
1090 peter@eisentraut.org 1235 :CBC 23 : sinfo->cstrs = palloc_array(char *, nfields);
1236 : : }
1237 : :
1238 : : /* Should have a single-row result if we get here */
4783 tgl@sss.pgh.pa.us 1239 [ - + ]: 154 : Assert(PQntuples(res) == 1);
1240 : :
1241 : : /*
1242 : : * Do the following work in a temp context that we reset after each tuple.
1243 : : * This cleans up not only the data we have direct access to, but any
1244 : : * cruft the I/O functions might leak.
1245 : : */
4903 1246 : 154 : oldcontext = MemoryContextSwitchTo(sinfo->tmpcontext);
1247 : :
1248 : : /*
1249 : : * Fill cstrs with null-terminated strings of column values.
1250 : : */
1251 [ + + ]: 574 : for (i = 0; i < nfields; i++)
1252 : : {
4783 1253 [ - + ]: 420 : if (PQgetisnull(res, 0, i))
4783 tgl@sss.pgh.pa.us 1254 :UBC 0 : sinfo->cstrs[i] = NULL;
1255 : : else
4783 tgl@sss.pgh.pa.us 1256 :CBC 420 : sinfo->cstrs[i] = PQgetvalue(res, 0, i);
1257 : : }
1258 : :
1259 : : /* Convert row to a tuple, and add it to the tuplestore */
1260 : 154 : tuple = BuildTupleFromCStrings(sinfo->attinmeta, sinfo->cstrs);
1261 : :
4903 1262 : 154 : tuplestore_puttuple(sinfo->tuplestore, tuple);
1263 : :
1264 : : /* Clean up */
1265 : 154 : MemoryContextSwitchTo(oldcontext);
1266 : 154 : MemoryContextReset(sinfo->tmpcontext);
1267 : : }
1268 : :
1269 : : /*
1270 : : * List all open dblink connections by name.
1271 : : * Returns an array of all connection names.
1272 : : * Takes no params
1273 : : */
6944 mail@joeconway.com 1274 : 4 : PG_FUNCTION_INFO_V1(dblink_get_connections);
1275 : : Datum
1276 : 1 : dblink_get_connections(PG_FUNCTION_ARGS)
1277 : : {
1278 : : HASH_SEQ_STATUS status;
1279 : : remoteConnHashEnt *hentry;
6912 bruce@momjian.us 1280 : 1 : ArrayBuildState *astate = NULL;
1281 : :
6944 mail@joeconway.com 1282 [ + - ]: 1 : if (remoteConnHash)
1283 : : {
1284 : 1 : hash_seq_init(&status, remoteConnHash);
1285 [ + + ]: 4 : while ((hentry = (remoteConnHashEnt *) hash_seq_search(&status)) != NULL)
1286 : : {
1287 : : /* ignore it if it's not an open connection */
100 tgl@sss.pgh.pa.us 1288 [ - + ]: 3 : if (hentry->rconn.conn == NULL)
100 tgl@sss.pgh.pa.us 1289 :UBC 0 : continue;
1290 : : /* stash away current value */
6944 mail@joeconway.com 1291 :CBC 3 : astate = accumArrayResult(astate,
6374 tgl@sss.pgh.pa.us 1292 : 3 : CStringGetTextDatum(hentry->name),
1293 : : false, TEXTOID, CurrentMemoryContext);
1294 : : }
1295 : : }
1296 : :
6944 mail@joeconway.com 1297 [ + - ]: 1 : if (astate)
1105 peter@eisentraut.org 1298 : 1 : PG_RETURN_DATUM(makeArrayResult(astate,
1299 : : CurrentMemoryContext));
1300 : : else
6944 mail@joeconway.com 1301 :UBC 0 : PG_RETURN_NULL();
1302 : : }
1303 : :
1304 : : /*
1305 : : * Checks if a given remote connection is busy
1306 : : *
1307 : : * Returns 1 if the connection is busy, 0 otherwise
1308 : : * Params:
1309 : : * text connection_name - name of the connection to check
1310 : : *
1311 : : */
6944 mail@joeconway.com 1312 :CBC 4 : PG_FUNCTION_INFO_V1(dblink_is_busy);
1313 : : Datum
1314 : 1 : dblink_is_busy(PG_FUNCTION_ARGS)
1315 : : {
1316 : : PGconn *conn;
1317 : :
3177 peter_e@gmx.net 1318 : 1 : dblink_init();
1319 : 1 : conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
1320 : :
6944 mail@joeconway.com 1321 : 1 : PQconsumeInput(conn);
1322 : 1 : PG_RETURN_INT32(PQisBusy(conn));
1323 : : }
1324 : :
1325 : : /*
1326 : : * Cancels a running request on a connection
1327 : : *
1328 : : * Returns text:
1329 : : * "OK" if the cancel request has been sent correctly,
1330 : : * an error message otherwise
1331 : : *
1332 : : * Params:
1333 : : * text connection_name - name of the connection to check
1334 : : *
1335 : : */
1336 : 4 : PG_FUNCTION_INFO_V1(dblink_cancel_query);
1337 : : Datum
1338 : 1 : dblink_cancel_query(PG_FUNCTION_ARGS)
1339 : : {
1340 : : PGconn *conn;
1341 : : const char *msg;
1342 : : TimestampTz endtime;
1343 : :
3177 peter_e@gmx.net 1344 : 1 : dblink_init();
1345 : 1 : conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
527 alvherre@alvh.no-ip. 1346 : 1 : endtime = TimestampTzPlusMilliseconds(GetCurrentTimestamp(),
1347 : : 30000);
1348 : 1 : msg = libpqsrv_cancel(conn, endtime);
1349 [ + - ]: 1 : if (msg == NULL)
1350 : 1 : msg = "OK";
1351 : :
537 1352 : 1 : PG_RETURN_TEXT_P(cstring_to_text(msg));
1353 : : }
1354 : :
1355 : :
1356 : : /*
1357 : : * Get error message from a connection
1358 : : *
1359 : : * Returns text:
1360 : : * "OK" if no error, an error message otherwise
1361 : : *
1362 : : * Params:
1363 : : * text connection_name - name of the connection to check
1364 : : *
1365 : : */
6944 mail@joeconway.com 1366 : 4 : PG_FUNCTION_INFO_V1(dblink_error_message);
1367 : : Datum
1368 : 1 : dblink_error_message(PG_FUNCTION_ARGS)
1369 : : {
1370 : : char *msg;
1371 : : PGconn *conn;
1372 : :
3177 peter_e@gmx.net 1373 : 1 : dblink_init();
1374 : 1 : conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
1375 : :
6944 mail@joeconway.com 1376 : 1 : msg = PQerrorMessage(conn);
6456 tgl@sss.pgh.pa.us 1377 [ + - + - ]: 1 : if (msg == NULL || msg[0] == '\0')
6374 1378 : 1 : PG_RETURN_TEXT_P(cstring_to_text("OK"));
1379 : : else
3113 peter_e@gmx.net 1380 :UBC 0 : PG_RETURN_TEXT_P(cstring_to_text(pchomp(msg)));
1381 : : }
1382 : :
1383 : : /*
1384 : : * Execute an SQL non-SELECT command
1385 : : */
8405 bruce@momjian.us 1386 :CBC 17 : PG_FUNCTION_INFO_V1(dblink_exec);
1387 : : Datum
1388 : 26 : dblink_exec(PG_FUNCTION_ARGS)
1389 : : {
4904 tgl@sss.pgh.pa.us 1390 : 26 : text *volatile sql_cmd_status = NULL;
1391 : 26 : PGconn *volatile conn = NULL;
1392 : 26 : volatile bool freeconn = false;
1393 : :
3177 peter_e@gmx.net 1394 : 26 : dblink_init();
1395 : :
4904 tgl@sss.pgh.pa.us 1396 [ + + ]: 26 : PG_TRY();
1397 : : {
1398 : 26 : PGresult *res = NULL;
1399 : 26 : char *sql = NULL;
1400 : 26 : char *conname = NULL;
1401 : 26 : bool fail = true; /* default to backward compatible behavior */
1402 : :
1403 [ - + ]: 26 : if (PG_NARGS() == 3)
1404 : : {
1405 : : /* must be text,text,bool */
3084 peter_e@gmx.net 1406 :UBC 0 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
4904 tgl@sss.pgh.pa.us 1407 : 0 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
1408 : 0 : fail = PG_GETARG_BOOL(2);
3084 peter_e@gmx.net 1409 : 0 : dblink_get_conn(conname, &conn, &conname, &freeconn);
1410 : : }
4904 tgl@sss.pgh.pa.us 1411 [ + + ]:CBC 26 : else if (PG_NARGS() == 2)
1412 : : {
1413 : : /* might be text,text or text,bool */
1414 [ + + ]: 17 : if (get_fn_expr_argtype(fcinfo->flinfo, 1) == BOOLOID)
1415 : : {
1416 : 1 : sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
1417 : 1 : fail = PG_GETARG_BOOL(1);
3084 peter_e@gmx.net 1418 : 1 : conn = pconn->conn;
1419 : : }
1420 : : else
1421 : : {
1422 : 16 : conname = text_to_cstring(PG_GETARG_TEXT_PP(0));
4904 tgl@sss.pgh.pa.us 1423 : 16 : sql = text_to_cstring(PG_GETARG_TEXT_PP(1));
3084 peter_e@gmx.net 1424 : 16 : dblink_get_conn(conname, &conn, &conname, &freeconn);
1425 : : }
1426 : : }
4904 tgl@sss.pgh.pa.us 1427 [ + - ]: 9 : else if (PG_NARGS() == 1)
1428 : : {
1429 : : /* must be single text argument */
7263 mail@joeconway.com 1430 : 9 : conn = pconn->conn;
6374 tgl@sss.pgh.pa.us 1431 : 9 : sql = text_to_cstring(PG_GETARG_TEXT_PP(0));
1432 : : }
1433 : : else
1434 : : /* shouldn't happen */
4904 tgl@sss.pgh.pa.us 1435 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments");
1436 : :
4904 tgl@sss.pgh.pa.us 1437 [ - + ]:CBC 26 : if (!conn)
3177 peter_e@gmx.net 1438 :UBC 0 : dblink_conn_not_avail(conname);
1439 : :
607 noah@leadboat.com 1440 :CBC 26 : res = libpqsrv_exec(conn, sql, dblink_we_get_result);
4904 tgl@sss.pgh.pa.us 1441 [ + - ]: 26 : if (!res ||
1442 [ + + ]: 26 : (PQresultStatus(res) != PGRES_COMMAND_OK &&
1443 [ + - ]: 2 : PQresultStatus(res) != PGRES_TUPLES_OK))
1444 : : {
2725 1445 : 2 : dblink_res_error(conn, conname, res, fail,
1446 : : "while executing command");
1447 : :
1448 : : /*
1449 : : * and save a copy of the command status string to return as our
1450 : : * result tuple
1451 : : */
4904 1452 : 1 : sql_cmd_status = cstring_to_text("ERROR");
1453 : : }
1454 [ + - ]: 24 : else if (PQresultStatus(res) == PGRES_COMMAND_OK)
1455 : : {
1456 : : /*
1457 : : * and save a copy of the command status string to return as our
1458 : : * result tuple
1459 : : */
1460 : 24 : sql_cmd_status = cstring_to_text(PQcmdStatus(res));
1461 : 24 : PQclear(res);
1462 : : }
1463 : : else
1464 : : {
4904 tgl@sss.pgh.pa.us 1465 :UBC 0 : PQclear(res);
1466 [ # # ]: 0 : ereport(ERROR,
1467 : : (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
1468 : : errmsg("statement returning results not allowed")));
1469 : : }
1470 : : }
2136 peter@eisentraut.org 1471 :CBC 1 : PG_FINALLY();
1472 : : {
1473 : : /* if needed, close the connection to the database */
4904 tgl@sss.pgh.pa.us 1474 [ + + ]: 26 : if (freeconn)
957 andres@anarazel.de 1475 : 1 : libpqsrv_disconnect(conn);
1476 : : }
4904 tgl@sss.pgh.pa.us 1477 [ + + ]: 26 : PG_END_TRY();
1478 : :
8109 bruce@momjian.us 1479 : 25 : PG_RETURN_TEXT_P(sql_cmd_status);
1480 : : }
1481 : :
1482 : :
1483 : : /*
1484 : : * dblink_get_pkey
1485 : : *
1486 : : * Return list of primary key fields for the supplied relation,
1487 : : * or NULL if none exists.
1488 : : */
8536 1489 : 4 : PG_FUNCTION_INFO_V1(dblink_get_pkey);
1490 : : Datum
1491 : 9 : dblink_get_pkey(PG_FUNCTION_ARGS)
1492 : : {
1493 : : int16 indnkeyatts;
1494 : : char **results;
1495 : : FuncCallContext *funcctx;
1496 : : int32 call_cntr;
1497 : : int32 max_calls;
1498 : : AttInMetadata *attinmeta;
1499 : : MemoryContext oldcontext;
1500 : :
1501 : : /* stuff done only on the first call of the function */
8403 1502 [ + + ]: 9 : if (SRF_IS_FIRSTCALL())
1503 : : {
1504 : : Relation rel;
1505 : : TupleDesc tupdesc;
1506 : :
1507 : : /* create a function context for cross-call persistence */
1508 : 3 : funcctx = SRF_FIRSTCALL_INIT();
1509 : :
1510 : : /*
1511 : : * switch to memory context appropriate for multiple function calls
1512 : : */
8405 1513 : 3 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1514 : :
1515 : : /* open target relation */
3100 noah@leadboat.com 1516 : 3 : rel = get_rel_from_relname(PG_GETARG_TEXT_PP(0), AccessShareLock, ACL_SELECT);
1517 : :
1518 : : /* get the array of attnums */
2709 teodor@sigaev.ru 1519 : 3 : results = get_pkey_attnames(rel, &indnkeyatts);
1520 : :
5563 tgl@sss.pgh.pa.us 1521 : 3 : relation_close(rel, AccessShareLock);
1522 : :
1523 : : /*
1524 : : * need a tuple descriptor representing one INT and one TEXT column
1525 : : */
2482 andres@anarazel.de 1526 : 3 : tupdesc = CreateTemplateTupleDesc(2);
8405 bruce@momjian.us 1527 : 3 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "position",
1528 : : INT4OID, -1, 0);
1529 : 3 : TupleDescInitEntry(tupdesc, (AttrNumber) 2, "colname",
1530 : : TEXTOID, -1, 0);
1531 : :
1532 : : /*
1533 : : * Generate attribute metadata needed later to produce tuples from raw
1534 : : * C strings
1535 : : */
1536 : 3 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
1537 : 3 : funcctx->attinmeta = attinmeta;
1538 : :
2709 teodor@sigaev.ru 1539 [ + - + - ]: 3 : if ((results != NULL) && (indnkeyatts > 0))
1540 : : {
1541 : 3 : funcctx->max_calls = indnkeyatts;
1542 : :
1543 : : /* got results, keep track of them */
8405 bruce@momjian.us 1544 : 3 : funcctx->user_fctx = results;
1545 : : }
1546 : : else
1547 : : {
1548 : : /* fast track when no results */
6124 tgl@sss.pgh.pa.us 1549 :UBC 0 : MemoryContextSwitchTo(oldcontext);
8403 bruce@momjian.us 1550 : 0 : SRF_RETURN_DONE(funcctx);
1551 : : }
1552 : :
8405 bruce@momjian.us 1553 :CBC 3 : MemoryContextSwitchTo(oldcontext);
1554 : : }
1555 : :
1556 : : /* stuff done on every call of the function */
8403 1557 : 9 : funcctx = SRF_PERCALL_SETUP();
1558 : :
1559 : : /*
1560 : : * initialize per-call variables
1561 : : */
8405 1562 : 9 : call_cntr = funcctx->call_cntr;
1563 : 9 : max_calls = funcctx->max_calls;
1564 : :
1565 : 9 : results = (char **) funcctx->user_fctx;
1566 : 9 : attinmeta = funcctx->attinmeta;
1567 : :
1568 [ + + ]: 9 : if (call_cntr < max_calls) /* do when there is more left to send */
1569 : : {
1570 : : char **values;
1571 : : HeapTuple tuple;
1572 : : Datum result;
1573 : :
1090 peter@eisentraut.org 1574 : 6 : values = palloc_array(char *, 2);
4261 peter_e@gmx.net 1575 : 6 : values[0] = psprintf("%d", call_cntr + 1);
8405 bruce@momjian.us 1576 : 6 : values[1] = results[call_cntr];
1577 : :
1578 : : /* build the tuple */
1579 : 6 : tuple = BuildTupleFromCStrings(attinmeta, values);
1580 : :
1581 : : /* make the tuple into a datum */
7828 tgl@sss.pgh.pa.us 1582 : 6 : result = HeapTupleGetDatum(tuple);
1583 : :
8403 bruce@momjian.us 1584 : 6 : SRF_RETURN_NEXT(funcctx, result);
1585 : : }
1586 : : else
1587 : : {
1588 : : /* do when there is no more left */
8109 1589 : 3 : SRF_RETURN_DONE(funcctx);
1590 : : }
1591 : : }
1592 : :
1593 : :
1594 : : /*
1595 : : * dblink_build_sql_insert
1596 : : *
1597 : : * Used to generate an SQL insert statement
1598 : : * based on an existing tuple in a local relation.
1599 : : * This is useful for selectively replicating data
1600 : : * to another server via dblink.
1601 : : *
1602 : : * API:
1603 : : * <relname> - name of local table of interest
1604 : : * <pkattnums> - an int2vector of attnums which will be used
1605 : : * to identify the local tuple of interest
1606 : : * <pknumatts> - number of attnums in pkattnums
1607 : : * <src_pkattvals_arry> - text array of key values which will be used
1608 : : * to identify the local tuple of interest
1609 : : * <tgt_pkattvals_arry> - text array of key values which will be used
1610 : : * to build the string for execution remotely. These are substituted
1611 : : * for their counterparts in src_pkattvals_arry
1612 : : */
8536 1613 : 5 : PG_FUNCTION_INFO_V1(dblink_build_sql_insert);
1614 : : Datum
1615 : 6 : dblink_build_sql_insert(PG_FUNCTION_ARGS)
1616 : : {
3100 noah@leadboat.com 1617 : 6 : text *relname_text = PG_GETARG_TEXT_PP(0);
5562 tgl@sss.pgh.pa.us 1618 : 6 : int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1);
1619 : 6 : int32 pknumatts_arg = PG_GETARG_INT32(2);
7232 1620 : 6 : ArrayType *src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
1621 : 6 : ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);
1622 : : Relation rel;
1623 : : int *pkattnums;
1624 : : int pknumatts;
1625 : : char **src_pkattvals;
1626 : : char **tgt_pkattvals;
1627 : : int src_nitems;
1628 : : int tgt_nitems;
1629 : : char *sql;
1630 : :
1631 : : /*
1632 : : * Open target relation.
1633 : : */
5563 1634 : 6 : rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
1635 : :
1636 : : /*
1637 : : * Process pkattnums argument.
1638 : : */
5562 1639 : 6 : validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
1640 : : &pkattnums, &pknumatts);
1641 : :
1642 : : /*
1643 : : * Source array is made up of key values that will be used to locate the
1644 : : * tuple of interest from the local system.
1645 : : */
7232 1646 : 4 : src_pkattvals = get_text_array_contents(src_pkattvals_arry, &src_nitems);
1647 : :
1648 : : /*
1649 : : * There should be one source array key value for each key attnum
1650 : : */
8536 bruce@momjian.us 1651 [ - + ]: 4 : if (src_nitems != pknumatts)
8080 tgl@sss.pgh.pa.us 1652 [ # # ]:UBC 0 : ereport(ERROR,
1653 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1654 : : errmsg("source key array length must match number of key attributes")));
1655 : :
1656 : : /*
1657 : : * Target array is made up of key values that will be used to build the
1658 : : * SQL string for use on the remote system.
1659 : : */
7232 tgl@sss.pgh.pa.us 1660 :CBC 4 : tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems);
1661 : :
1662 : : /*
1663 : : * There should be one target array key value for each key attnum
1664 : : */
8536 bruce@momjian.us 1665 [ - + ]: 4 : if (tgt_nitems != pknumatts)
8080 tgl@sss.pgh.pa.us 1666 [ # # ]:UBC 0 : ereport(ERROR,
1667 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1668 : : errmsg("target key array length must match number of key attributes")));
1669 : :
1670 : : /*
1671 : : * Prep work is finally done. Go get the SQL string.
1672 : : */
5563 tgl@sss.pgh.pa.us 1673 :CBC 4 : sql = get_sql_insert(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);
1674 : :
1675 : : /*
1676 : : * Now we can close the relation.
1677 : : */
1678 : 4 : relation_close(rel, AccessShareLock);
1679 : :
1680 : : /*
1681 : : * And send it
1682 : : */
6374 1683 : 4 : PG_RETURN_TEXT_P(cstring_to_text(sql));
1684 : : }
1685 : :
1686 : :
1687 : : /*
1688 : : * dblink_build_sql_delete
1689 : : *
1690 : : * Used to generate an SQL delete statement.
1691 : : * This is useful for selectively replicating a
1692 : : * delete to another server via dblink.
1693 : : *
1694 : : * API:
1695 : : * <relname> - name of remote table of interest
1696 : : * <pkattnums> - an int2vector of attnums which will be used
1697 : : * to identify the remote tuple of interest
1698 : : * <pknumatts> - number of attnums in pkattnums
1699 : : * <tgt_pkattvals_arry> - text array of key values which will be used
1700 : : * to build the string for execution remotely.
1701 : : */
8536 bruce@momjian.us 1702 : 5 : PG_FUNCTION_INFO_V1(dblink_build_sql_delete);
1703 : : Datum
1704 : 6 : dblink_build_sql_delete(PG_FUNCTION_ARGS)
1705 : : {
3100 noah@leadboat.com 1706 : 6 : text *relname_text = PG_GETARG_TEXT_PP(0);
5562 tgl@sss.pgh.pa.us 1707 : 6 : int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1);
1708 : 6 : int32 pknumatts_arg = PG_GETARG_INT32(2);
7232 1709 : 6 : ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
1710 : : Relation rel;
1711 : : int *pkattnums;
1712 : : int pknumatts;
1713 : : char **tgt_pkattvals;
1714 : : int tgt_nitems;
1715 : : char *sql;
1716 : :
1717 : : /*
1718 : : * Open target relation.
1719 : : */
5563 1720 : 6 : rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
1721 : :
1722 : : /*
1723 : : * Process pkattnums argument.
1724 : : */
5562 1725 : 6 : validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
1726 : : &pkattnums, &pknumatts);
1727 : :
1728 : : /*
1729 : : * Target array is made up of key values that will be used to build the
1730 : : * SQL string for use on the remote system.
1731 : : */
7232 1732 : 4 : tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems);
1733 : :
1734 : : /*
1735 : : * There should be one target array key value for each key attnum
1736 : : */
8536 bruce@momjian.us 1737 [ - + ]: 4 : if (tgt_nitems != pknumatts)
8080 tgl@sss.pgh.pa.us 1738 [ # # ]:UBC 0 : ereport(ERROR,
1739 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1740 : : errmsg("target key array length must match number of key attributes")));
1741 : :
1742 : : /*
1743 : : * Prep work is finally done. Go get the SQL string.
1744 : : */
5563 tgl@sss.pgh.pa.us 1745 :CBC 4 : sql = get_sql_delete(rel, pkattnums, pknumatts, tgt_pkattvals);
1746 : :
1747 : : /*
1748 : : * Now we can close the relation.
1749 : : */
1750 : 4 : relation_close(rel, AccessShareLock);
1751 : :
1752 : : /*
1753 : : * And send it
1754 : : */
6374 1755 : 4 : PG_RETURN_TEXT_P(cstring_to_text(sql));
1756 : : }
1757 : :
1758 : :
1759 : : /*
1760 : : * dblink_build_sql_update
1761 : : *
1762 : : * Used to generate an SQL update statement
1763 : : * based on an existing tuple in a local relation.
1764 : : * This is useful for selectively replicating data
1765 : : * to another server via dblink.
1766 : : *
1767 : : * API:
1768 : : * <relname> - name of local table of interest
1769 : : * <pkattnums> - an int2vector of attnums which will be used
1770 : : * to identify the local tuple of interest
1771 : : * <pknumatts> - number of attnums in pkattnums
1772 : : * <src_pkattvals_arry> - text array of key values which will be used
1773 : : * to identify the local tuple of interest
1774 : : * <tgt_pkattvals_arry> - text array of key values which will be used
1775 : : * to build the string for execution remotely. These are substituted
1776 : : * for their counterparts in src_pkattvals_arry
1777 : : */
8536 bruce@momjian.us 1778 : 5 : PG_FUNCTION_INFO_V1(dblink_build_sql_update);
1779 : : Datum
1780 : 6 : dblink_build_sql_update(PG_FUNCTION_ARGS)
1781 : : {
3100 noah@leadboat.com 1782 : 6 : text *relname_text = PG_GETARG_TEXT_PP(0);
5562 tgl@sss.pgh.pa.us 1783 : 6 : int2vector *pkattnums_arg = (int2vector *) PG_GETARG_POINTER(1);
1784 : 6 : int32 pknumatts_arg = PG_GETARG_INT32(2);
7232 1785 : 6 : ArrayType *src_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(3);
1786 : 6 : ArrayType *tgt_pkattvals_arry = PG_GETARG_ARRAYTYPE_P(4);
1787 : : Relation rel;
1788 : : int *pkattnums;
1789 : : int pknumatts;
1790 : : char **src_pkattvals;
1791 : : char **tgt_pkattvals;
1792 : : int src_nitems;
1793 : : int tgt_nitems;
1794 : : char *sql;
1795 : :
1796 : : /*
1797 : : * Open target relation.
1798 : : */
5563 1799 : 6 : rel = get_rel_from_relname(relname_text, AccessShareLock, ACL_SELECT);
1800 : :
1801 : : /*
1802 : : * Process pkattnums argument.
1803 : : */
5562 1804 : 6 : validate_pkattnums(rel, pkattnums_arg, pknumatts_arg,
1805 : : &pkattnums, &pknumatts);
1806 : :
1807 : : /*
1808 : : * Source array is made up of key values that will be used to locate the
1809 : : * tuple of interest from the local system.
1810 : : */
7232 1811 : 4 : src_pkattvals = get_text_array_contents(src_pkattvals_arry, &src_nitems);
1812 : :
1813 : : /*
1814 : : * There should be one source array key value for each key attnum
1815 : : */
8536 bruce@momjian.us 1816 [ - + ]: 4 : if (src_nitems != pknumatts)
8080 tgl@sss.pgh.pa.us 1817 [ # # ]:UBC 0 : ereport(ERROR,
1818 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1819 : : errmsg("source key array length must match number of key attributes")));
1820 : :
1821 : : /*
1822 : : * Target array is made up of key values that will be used to build the
1823 : : * SQL string for use on the remote system.
1824 : : */
7232 tgl@sss.pgh.pa.us 1825 :CBC 4 : tgt_pkattvals = get_text_array_contents(tgt_pkattvals_arry, &tgt_nitems);
1826 : :
1827 : : /*
1828 : : * There should be one target array key value for each key attnum
1829 : : */
8536 bruce@momjian.us 1830 [ - + ]: 4 : if (tgt_nitems != pknumatts)
8080 tgl@sss.pgh.pa.us 1831 [ # # ]:UBC 0 : ereport(ERROR,
1832 : : (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
1833 : : errmsg("target key array length must match number of key attributes")));
1834 : :
1835 : : /*
1836 : : * Prep work is finally done. Go get the SQL string.
1837 : : */
5563 tgl@sss.pgh.pa.us 1838 :CBC 4 : sql = get_sql_update(rel, pkattnums, pknumatts, src_pkattvals, tgt_pkattvals);
1839 : :
1840 : : /*
1841 : : * Now we can close the relation.
1842 : : */
1843 : 4 : relation_close(rel, AccessShareLock);
1844 : :
1845 : : /*
1846 : : * And send it
1847 : : */
6374 1848 : 4 : PG_RETURN_TEXT_P(cstring_to_text(sql));
1849 : : }
1850 : :
1851 : : /*
1852 : : * dblink_current_query
1853 : : * return the current query string
1854 : : * to allow its use in (among other things)
1855 : : * rewrite rules
1856 : : */
5933 1857 : 3 : PG_FUNCTION_INFO_V1(dblink_current_query);
1858 : : Datum
5933 tgl@sss.pgh.pa.us 1859 :UBC 0 : dblink_current_query(PG_FUNCTION_ARGS)
1860 : : {
1861 : : /* This is now just an alias for the built-in function current_query() */
1862 : 0 : PG_RETURN_DATUM(current_query(fcinfo));
1863 : : }
1864 : :
1865 : : /*
1866 : : * Retrieve async notifications for a connection.
1867 : : *
1868 : : * Returns a setof record of notifications, or an empty set if none received.
1869 : : * Can optionally take a named connection as parameter, but uses the unnamed
1870 : : * connection per default.
1871 : : *
1872 : : */
1873 : : #define DBLINK_NOTIFY_COLS 3
1874 : :
5876 mail@joeconway.com 1875 :CBC 7 : PG_FUNCTION_INFO_V1(dblink_get_notify);
1876 : : Datum
1877 : 2 : dblink_get_notify(PG_FUNCTION_ARGS)
1878 : : {
1879 : : PGconn *conn;
1880 : : PGnotify *notify;
5671 bruce@momjian.us 1881 : 2 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1882 : :
3177 peter_e@gmx.net 1883 : 2 : dblink_init();
5876 mail@joeconway.com 1884 [ - + ]: 2 : if (PG_NARGS() == 1)
3177 peter_e@gmx.net 1885 :UBC 0 : conn = dblink_get_named_conn(text_to_cstring(PG_GETARG_TEXT_PP(0)));
1886 : : else
5876 mail@joeconway.com 1887 :CBC 2 : conn = pconn->conn;
1888 : :
1054 michael@paquier.xyz 1889 : 2 : InitMaterializedSRF(fcinfo, 0);
1890 : :
5876 mail@joeconway.com 1891 : 2 : PQconsumeInput(conn);
1892 [ + + ]: 4 : while ((notify = PQnotifies(conn)) != NULL)
1893 : : {
1894 : : Datum values[DBLINK_NOTIFY_COLS];
1895 : : bool nulls[DBLINK_NOTIFY_COLS];
1896 : :
1897 : 2 : memset(values, 0, sizeof(values));
1898 : 2 : memset(nulls, 0, sizeof(nulls));
1899 : :
1900 [ + - ]: 2 : if (notify->relname != NULL)
1901 : 2 : values[0] = CStringGetTextDatum(notify->relname);
1902 : : else
5876 mail@joeconway.com 1903 :UBC 0 : nulls[0] = true;
1904 : :
5876 mail@joeconway.com 1905 :CBC 2 : values[1] = Int32GetDatum(notify->be_pid);
1906 : :
1907 [ + - ]: 2 : if (notify->extra != NULL)
1908 : 2 : values[2] = CStringGetTextDatum(notify->extra);
1909 : : else
5876 mail@joeconway.com 1910 :UBC 0 : nulls[2] = true;
1911 : :
1278 michael@paquier.xyz 1912 :CBC 2 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
1913 : :
5876 mail@joeconway.com 1914 : 2 : PQfreemem(notify);
1915 : 2 : PQconsumeInput(conn);
1916 : : }
1917 : :
1918 : 2 : return (Datum) 0;
1919 : : }
1920 : :
1921 : : /*
1922 : : * Validate the options given to a dblink foreign server or user mapping.
1923 : : * Raise an error if any option is invalid.
1924 : : *
1925 : : * We just check the names of options here, so semantic errors in options,
1926 : : * such as invalid numeric format, will be detected at the attempt to connect.
1927 : : */
4714 tgl@sss.pgh.pa.us 1928 : 16 : PG_FUNCTION_INFO_V1(dblink_fdw_validator);
1929 : : Datum
1930 : 20 : dblink_fdw_validator(PG_FUNCTION_ARGS)
1931 : : {
1932 : 20 : List *options_list = untransformRelOptions(PG_GETARG_DATUM(0));
1933 : 20 : Oid context = PG_GETARG_OID(1);
1934 : : ListCell *cell;
1935 : :
1936 : : static const PQconninfoOption *options = NULL;
1937 : :
1938 : : /*
1939 : : * Get list of valid libpq options.
1940 : : *
1941 : : * To avoid unnecessary work, we get the list once and use it throughout
1942 : : * the lifetime of this backend process. We don't need to care about
1943 : : * memory context issues, because PQconndefaults allocates with malloc.
1944 : : */
1945 [ + + ]: 20 : if (!options)
1946 : : {
1947 : 13 : options = PQconndefaults();
1948 [ - + ]: 13 : if (!options) /* assume reason for failure is OOM */
4714 tgl@sss.pgh.pa.us 1949 [ # # ]:UBC 0 : ereport(ERROR,
1950 : : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
1951 : : errmsg("out of memory"),
1952 : : errdetail("Could not get libpq's default connection options.")));
1953 : : }
1954 : :
1955 : : /* Validate each supplied option. */
4714 tgl@sss.pgh.pa.us 1956 [ + + + + :CBC 51 : foreach(cell, options_list)
+ + ]
1957 : : {
1958 : 39 : DefElem *def = (DefElem *) lfirst(cell);
1959 : :
164 peter@eisentraut.org 1960 [ + + ]: 39 : if (!is_valid_dblink_fdw_option(options, def->defname, context))
1961 : : {
1962 : : /*
1963 : : * Unknown option, or invalid option for the context specified, so
1964 : : * complain about it. Provide a hint with a valid option that
1965 : : * looks similar, if there is one.
1966 : : */
1967 : : const PQconninfoOption *opt;
1968 : : const char *closest_match;
1969 : : ClosestMatchState match_state;
1086 1970 : 8 : bool has_valid_options = false;
1971 : :
1972 : 8 : initClosestMatch(&match_state, def->defname, 4);
4714 tgl@sss.pgh.pa.us 1973 [ + + ]: 416 : for (opt = options; opt->keyword; opt++)
1974 : : {
1975 [ + + ]: 408 : if (is_valid_dblink_option(options, opt->keyword, context))
1976 : : {
1086 peter@eisentraut.org 1977 : 93 : has_valid_options = true;
1978 : 93 : updateClosestMatch(&match_state, opt->keyword);
1979 : : }
1980 : : }
1981 : :
1982 : 8 : closest_match = getClosestMatch(&match_state);
4714 tgl@sss.pgh.pa.us 1983 [ + - + + : 8 : ereport(ERROR,
- + ]
1984 : : (errcode(ERRCODE_FDW_OPTION_NAME_NOT_FOUND),
1985 : : errmsg("invalid option \"%s\"", def->defname),
1986 : : has_valid_options ? closest_match ?
1987 : : errhint("Perhaps you meant the option \"%s\".",
1988 : : closest_match) : 0 :
1989 : : errhint("There are no valid options in this context.")));
1990 : : }
1991 : : }
1992 : :
1993 : 12 : PG_RETURN_VOID();
1994 : : }
1995 : :
1996 : :
1997 : : /*************************************************************
1998 : : * internal functions
1999 : : */
2000 : :
2001 : :
2002 : : /*
2003 : : * get_pkey_attnames
2004 : : *
2005 : : * Get the primary key attnames for the given relation.
2006 : : * Return NULL, and set indnkeyatts = 0, if no primary key exists.
2007 : : */
2008 : : static char **
2709 teodor@sigaev.ru 2009 : 3 : get_pkey_attnames(Relation rel, int16 *indnkeyatts)
2010 : : {
2011 : : Relation indexRelation;
2012 : : ScanKeyData skey;
2013 : : SysScanDesc scan;
2014 : : HeapTuple indexTuple;
2015 : : int i;
8403 bruce@momjian.us 2016 : 3 : char **result = NULL;
2017 : : TupleDesc tupdesc;
2018 : :
2019 : : /* initialize indnkeyatts to 0 in case no primary key exists */
2709 teodor@sigaev.ru 2020 : 3 : *indnkeyatts = 0;
2021 : :
8536 bruce@momjian.us 2022 : 3 : tupdesc = rel->rd_att;
2023 : :
2024 : : /* Prepare to scan pg_index for entries having indrelid = this rel. */
2420 andres@anarazel.de 2025 : 3 : indexRelation = table_open(IndexRelationId, AccessShareLock);
6445 tgl@sss.pgh.pa.us 2026 : 3 : ScanKeyInit(&skey,
2027 : : Anum_pg_index_indrelid,
2028 : : BTEqualStrategyNumber, F_OIDEQ,
2029 : : ObjectIdGetDatum(RelationGetRelid(rel)));
2030 : :
2031 : 3 : scan = systable_beginscan(indexRelation, IndexIndrelidIndexId, true,
2032 : : NULL, 1, &skey);
2033 : :
2034 [ + - ]: 3 : while (HeapTupleIsValid(indexTuple = systable_getnext(scan)))
2035 : : {
8403 bruce@momjian.us 2036 : 3 : Form_pg_index index = (Form_pg_index) GETSTRUCT(indexTuple);
2037 : :
2038 : : /* we're only interested if it is the primary key */
6445 tgl@sss.pgh.pa.us 2039 [ + - ]: 3 : if (index->indisprimary)
2040 : : {
2709 teodor@sigaev.ru 2041 : 3 : *indnkeyatts = index->indnkeyatts;
2042 [ + - ]: 3 : if (*indnkeyatts > 0)
2043 : : {
1090 peter@eisentraut.org 2044 : 3 : result = palloc_array(char *, *indnkeyatts);
2045 : :
2709 teodor@sigaev.ru 2046 [ + + ]: 9 : for (i = 0; i < *indnkeyatts; i++)
7466 tgl@sss.pgh.pa.us 2047 : 6 : result[i] = SPI_fname(tupdesc, index->indkey.values[i]);
2048 : : }
8536 bruce@momjian.us 2049 : 3 : break;
2050 : : }
2051 : : }
2052 : :
6445 tgl@sss.pgh.pa.us 2053 : 3 : systable_endscan(scan);
2420 andres@anarazel.de 2054 : 3 : table_close(indexRelation, AccessShareLock);
2055 : :
8536 bruce@momjian.us 2056 : 3 : return result;
2057 : : }
2058 : :
2059 : : /*
2060 : : * Deconstruct a text[] into C-strings (note any NULL elements will be
2061 : : * returned as NULL pointers)
2062 : : */
2063 : : static char **
7232 tgl@sss.pgh.pa.us 2064 : 20 : get_text_array_contents(ArrayType *array, int *numitems)
2065 : : {
2066 : 20 : int ndim = ARR_NDIM(array);
2067 : 20 : int *dims = ARR_DIMS(array);
2068 : : int nitems;
2069 : : int16 typlen;
2070 : : bool typbyval;
2071 : : char typalign;
2072 : : char **values;
2073 : : char *ptr;
2074 : : bits8 *bitmap;
2075 : : int bitmask;
2076 : : int i;
2077 : :
2078 [ - + ]: 20 : Assert(ARR_ELEMTYPE(array) == TEXTOID);
2079 : :
2080 : 20 : *numitems = nitems = ArrayGetNItems(ndim, dims);
2081 : :
2082 : 20 : get_typlenbyvalalign(ARR_ELEMTYPE(array),
2083 : : &typlen, &typbyval, &typalign);
2084 : :
1090 peter@eisentraut.org 2085 : 20 : values = palloc_array(char *, nitems);
2086 : :
7232 tgl@sss.pgh.pa.us 2087 [ - + ]: 20 : ptr = ARR_DATA_PTR(array);
2088 [ - + ]: 20 : bitmap = ARR_NULLBITMAP(array);
2089 : 20 : bitmask = 1;
2090 : :
2091 [ + + ]: 55 : for (i = 0; i < nitems; i++)
2092 : : {
2093 [ - + - - ]: 35 : if (bitmap && (*bitmap & bitmask) == 0)
2094 : : {
7232 tgl@sss.pgh.pa.us 2095 :UBC 0 : values[i] = NULL;
2096 : : }
2097 : : else
2098 : : {
6374 tgl@sss.pgh.pa.us 2099 :CBC 35 : values[i] = TextDatumGetCString(PointerGetDatum(ptr));
6728 2100 [ - + + - : 35 : ptr = att_addlength_pointer(ptr, typlen, ptr);
- - - - -
- - - - +
- - ]
2101 [ + - - - : 35 : ptr = (char *) att_align_nominal(ptr, typalign);
- - - - ]
2102 : : }
2103 : :
2104 : : /* advance bitmap pointer if any */
7232 2105 [ - + ]: 35 : if (bitmap)
2106 : : {
7232 tgl@sss.pgh.pa.us 2107 :UBC 0 : bitmask <<= 1;
2108 [ # # ]: 0 : if (bitmask == 0x100)
2109 : : {
2110 : 0 : bitmap++;
2111 : 0 : bitmask = 1;
2112 : : }
2113 : : }
2114 : : }
2115 : :
7232 tgl@sss.pgh.pa.us 2116 :CBC 20 : return values;
2117 : : }
2118 : :
2119 : : static char *
5562 2120 : 4 : get_sql_insert(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
2121 : : {
2122 : : char *relname;
2123 : : HeapTuple tuple;
2124 : : TupleDesc tupdesc;
2125 : : int natts;
2126 : : StringInfoData buf;
2127 : : char *val;
2128 : : int key;
2129 : : int i;
2130 : : bool needComma;
2131 : :
7129 neilc@samurai.com 2132 : 4 : initStringInfo(&buf);
2133 : :
2134 : : /* get relation name including any needed schema prefix and quoting */
5563 tgl@sss.pgh.pa.us 2135 : 4 : relname = generate_relation_name(rel);
2136 : :
8536 bruce@momjian.us 2137 : 4 : tupdesc = rel->rd_att;
2138 : 4 : natts = tupdesc->natts;
2139 : :
5563 tgl@sss.pgh.pa.us 2140 : 4 : tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
8405 bruce@momjian.us 2141 [ - + ]: 4 : if (!tuple)
8080 tgl@sss.pgh.pa.us 2142 [ # # ]:UBC 0 : ereport(ERROR,
2143 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
2144 : : errmsg("source row not found")));
2145 : :
7129 neilc@samurai.com 2146 :CBC 4 : appendStringInfo(&buf, "INSERT INTO %s(", relname);
2147 : :
8436 tgl@sss.pgh.pa.us 2148 : 4 : needComma = false;
8536 bruce@momjian.us 2149 [ + + ]: 19 : for (i = 0; i < natts; i++)
2150 : : {
2939 andres@anarazel.de 2151 : 15 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
2152 : :
2153 [ + + ]: 15 : if (att->attisdropped)
8436 tgl@sss.pgh.pa.us 2154 : 2 : continue;
2155 : :
2156 [ + + ]: 13 : if (needComma)
4328 rhaas@postgresql.org 2157 : 9 : appendStringInfoChar(&buf, ',');
2158 : :
7129 neilc@samurai.com 2159 : 13 : appendStringInfoString(&buf,
2939 andres@anarazel.de 2160 : 13 : quote_ident_cstr(NameStr(att->attname)));
8436 tgl@sss.pgh.pa.us 2161 : 13 : needComma = true;
2162 : : }
2163 : :
4328 rhaas@postgresql.org 2164 : 4 : appendStringInfoString(&buf, ") VALUES(");
2165 : :
2166 : : /*
2167 : : * Note: i is physical column number (counting from 0).
2168 : : */
8436 tgl@sss.pgh.pa.us 2169 : 4 : needComma = false;
8536 bruce@momjian.us 2170 [ + + ]: 19 : for (i = 0; i < natts; i++)
2171 : : {
2939 andres@anarazel.de 2172 [ + + ]: 15 : if (TupleDescAttr(tupdesc, i)->attisdropped)
8436 tgl@sss.pgh.pa.us 2173 : 2 : continue;
2174 : :
2175 [ + + ]: 13 : if (needComma)
4328 rhaas@postgresql.org 2176 : 9 : appendStringInfoChar(&buf, ',');
2177 : :
5562 tgl@sss.pgh.pa.us 2178 : 13 : key = get_attnum_pk_pos(pkattnums, pknumatts, i);
2179 : :
2180 [ + + ]: 13 : if (key >= 0)
7232 2181 [ + - ]: 7 : val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL;
2182 : : else
8536 bruce@momjian.us 2183 : 6 : val = SPI_getvalue(tuple, tupdesc, i + 1);
2184 : :
2185 [ + - ]: 13 : if (val != NULL)
2186 : : {
7129 neilc@samurai.com 2187 : 13 : appendStringInfoString(&buf, quote_literal_cstr(val));
8536 bruce@momjian.us 2188 : 13 : pfree(val);
2189 : : }
2190 : : else
4328 rhaas@postgresql.org 2191 :UBC 0 : appendStringInfoString(&buf, "NULL");
8436 tgl@sss.pgh.pa.us 2192 :CBC 13 : needComma = true;
2193 : : }
4328 rhaas@postgresql.org 2194 : 4 : appendStringInfoChar(&buf, ')');
2195 : :
2942 peter_e@gmx.net 2196 : 4 : return buf.data;
2197 : : }
2198 : :
2199 : : static char *
5562 tgl@sss.pgh.pa.us 2200 : 4 : get_sql_delete(Relation rel, int *pkattnums, int pknumatts, char **tgt_pkattvals)
2201 : : {
2202 : : char *relname;
2203 : : TupleDesc tupdesc;
2204 : : StringInfoData buf;
2205 : : int i;
2206 : :
7129 neilc@samurai.com 2207 : 4 : initStringInfo(&buf);
2208 : :
2209 : : /* get relation name including any needed schema prefix and quoting */
5563 tgl@sss.pgh.pa.us 2210 : 4 : relname = generate_relation_name(rel);
2211 : :
8536 bruce@momjian.us 2212 : 4 : tupdesc = rel->rd_att;
2213 : :
7129 neilc@samurai.com 2214 : 4 : appendStringInfo(&buf, "DELETE FROM %s WHERE ", relname);
8536 bruce@momjian.us 2215 [ + + ]: 11 : for (i = 0; i < pknumatts; i++)
2216 : : {
5562 tgl@sss.pgh.pa.us 2217 : 7 : int pkattnum = pkattnums[i];
2939 andres@anarazel.de 2218 : 7 : Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
2219 : :
8536 bruce@momjian.us 2220 [ + + ]: 7 : if (i > 0)
4328 rhaas@postgresql.org 2221 : 3 : appendStringInfoString(&buf, " AND ");
2222 : :
7129 neilc@samurai.com 2223 : 7 : appendStringInfoString(&buf,
2939 andres@anarazel.de 2224 : 7 : quote_ident_cstr(NameStr(attr->attname)));
2225 : :
7232 tgl@sss.pgh.pa.us 2226 [ + - ]: 7 : if (tgt_pkattvals[i] != NULL)
7129 neilc@samurai.com 2227 : 7 : appendStringInfo(&buf, " = %s",
7232 tgl@sss.pgh.pa.us 2228 : 7 : quote_literal_cstr(tgt_pkattvals[i]));
2229 : : else
4328 rhaas@postgresql.org 2230 :UBC 0 : appendStringInfoString(&buf, " IS NULL");
2231 : : }
2232 : :
2942 peter_e@gmx.net 2233 :CBC 4 : return buf.data;
2234 : : }
2235 : :
2236 : : static char *
5562 tgl@sss.pgh.pa.us 2237 : 4 : get_sql_update(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals, char **tgt_pkattvals)
2238 : : {
2239 : : char *relname;
2240 : : HeapTuple tuple;
2241 : : TupleDesc tupdesc;
2242 : : int natts;
2243 : : StringInfoData buf;
2244 : : char *val;
2245 : : int key;
2246 : : int i;
2247 : : bool needComma;
2248 : :
7129 neilc@samurai.com 2249 : 4 : initStringInfo(&buf);
2250 : :
2251 : : /* get relation name including any needed schema prefix and quoting */
5563 tgl@sss.pgh.pa.us 2252 : 4 : relname = generate_relation_name(rel);
2253 : :
8536 bruce@momjian.us 2254 : 4 : tupdesc = rel->rd_att;
2255 : 4 : natts = tupdesc->natts;
2256 : :
5563 tgl@sss.pgh.pa.us 2257 : 4 : tuple = get_tuple_of_interest(rel, pkattnums, pknumatts, src_pkattvals);
8405 bruce@momjian.us 2258 [ - + ]: 4 : if (!tuple)
8080 tgl@sss.pgh.pa.us 2259 [ # # ]:UBC 0 : ereport(ERROR,
2260 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
2261 : : errmsg("source row not found")));
2262 : :
7129 neilc@samurai.com 2263 :CBC 4 : appendStringInfo(&buf, "UPDATE %s SET ", relname);
2264 : :
2265 : : /*
2266 : : * Note: i is physical column number (counting from 0).
2267 : : */
8436 tgl@sss.pgh.pa.us 2268 : 4 : needComma = false;
8536 bruce@momjian.us 2269 [ + + ]: 19 : for (i = 0; i < natts; i++)
2270 : : {
2939 andres@anarazel.de 2271 : 15 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
2272 : :
2273 [ + + ]: 15 : if (attr->attisdropped)
8436 tgl@sss.pgh.pa.us 2274 : 2 : continue;
2275 : :
2276 [ + + ]: 13 : if (needComma)
4328 rhaas@postgresql.org 2277 : 9 : appendStringInfoString(&buf, ", ");
2278 : :
7129 neilc@samurai.com 2279 : 13 : appendStringInfo(&buf, "%s = ",
2939 andres@anarazel.de 2280 : 13 : quote_ident_cstr(NameStr(attr->attname)));
2281 : :
5562 tgl@sss.pgh.pa.us 2282 : 13 : key = get_attnum_pk_pos(pkattnums, pknumatts, i);
2283 : :
2284 [ + + ]: 13 : if (key >= 0)
7228 bruce@momjian.us 2285 [ + - ]: 7 : val = tgt_pkattvals[key] ? pstrdup(tgt_pkattvals[key]) : NULL;
2286 : : else
8536 2287 : 6 : val = SPI_getvalue(tuple, tupdesc, i + 1);
2288 : :
2289 [ + - ]: 13 : if (val != NULL)
2290 : : {
7129 neilc@samurai.com 2291 : 13 : appendStringInfoString(&buf, quote_literal_cstr(val));
8536 bruce@momjian.us 2292 : 13 : pfree(val);
2293 : : }
2294 : : else
7129 neilc@samurai.com 2295 :UBC 0 : appendStringInfoString(&buf, "NULL");
8436 tgl@sss.pgh.pa.us 2296 :CBC 13 : needComma = true;
2297 : : }
2298 : :
4328 rhaas@postgresql.org 2299 : 4 : appendStringInfoString(&buf, " WHERE ");
2300 : :
8536 bruce@momjian.us 2301 [ + + ]: 11 : for (i = 0; i < pknumatts; i++)
2302 : : {
5562 tgl@sss.pgh.pa.us 2303 : 7 : int pkattnum = pkattnums[i];
2939 andres@anarazel.de 2304 : 7 : Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
2305 : :
8536 bruce@momjian.us 2306 [ + + ]: 7 : if (i > 0)
4328 rhaas@postgresql.org 2307 : 3 : appendStringInfoString(&buf, " AND ");
2308 : :
2309 : 7 : appendStringInfoString(&buf,
2939 andres@anarazel.de 2310 : 7 : quote_ident_cstr(NameStr(attr->attname)));
2311 : :
5562 tgl@sss.pgh.pa.us 2312 : 7 : val = tgt_pkattvals[i];
2313 : :
8536 bruce@momjian.us 2314 [ + - ]: 7 : if (val != NULL)
7129 neilc@samurai.com 2315 : 7 : appendStringInfo(&buf, " = %s", quote_literal_cstr(val));
2316 : : else
4328 rhaas@postgresql.org 2317 :UBC 0 : appendStringInfoString(&buf, " IS NULL");
2318 : : }
2319 : :
2942 peter_e@gmx.net 2320 :CBC 4 : return buf.data;
2321 : : }
2322 : :
2323 : : /*
2324 : : * Return a properly quoted identifier.
2325 : : * Uses quote_ident in quote.c
2326 : : */
2327 : : static char *
8536 bruce@momjian.us 2328 : 80 : quote_ident_cstr(char *rawstr)
2329 : : {
2330 : : text *rawstr_text;
2331 : : text *result_text;
2332 : : char *result;
2333 : :
6374 tgl@sss.pgh.pa.us 2334 : 80 : rawstr_text = cstring_to_text(rawstr);
3100 noah@leadboat.com 2335 : 80 : result_text = DatumGetTextPP(DirectFunctionCall1(quote_ident,
2336 : : PointerGetDatum(rawstr_text)));
6374 tgl@sss.pgh.pa.us 2337 : 80 : result = text_to_cstring(result_text);
2338 : :
8536 bruce@momjian.us 2339 : 80 : return result;
2340 : : }
2341 : :
2342 : : static int
5562 tgl@sss.pgh.pa.us 2343 : 26 : get_attnum_pk_pos(int *pkattnums, int pknumatts, int key)
2344 : : {
2345 : : int i;
2346 : :
2347 : : /*
2348 : : * Not likely a long list anyway, so just scan for the value
2349 : : */
8536 bruce@momjian.us 2350 [ + + ]: 50 : for (i = 0; i < pknumatts; i++)
5562 tgl@sss.pgh.pa.us 2351 [ + + ]: 38 : if (key == pkattnums[i])
8536 bruce@momjian.us 2352 : 14 : return i;
2353 : :
2354 : 12 : return -1;
2355 : : }
2356 : :
2357 : : static HeapTuple
5562 tgl@sss.pgh.pa.us 2358 : 8 : get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals)
2359 : : {
2360 : : char *relname;
2361 : : TupleDesc tupdesc;
2362 : : int natts;
2363 : : StringInfoData buf;
2364 : : int ret;
2365 : : HeapTuple tuple;
2366 : : int i;
2367 : :
2368 : : /*
2369 : : * Connect to SPI manager
2370 : : */
362 2371 : 8 : SPI_connect();
2372 : :
7129 neilc@samurai.com 2373 : 8 : initStringInfo(&buf);
2374 : :
2375 : : /* get relation name including any needed schema prefix and quoting */
5563 tgl@sss.pgh.pa.us 2376 : 8 : relname = generate_relation_name(rel);
2377 : :
2378 : 8 : tupdesc = rel->rd_att;
5562 2379 : 8 : natts = tupdesc->natts;
2380 : :
2381 : : /*
2382 : : * Build sql statement to look up tuple of interest, ie, the one matching
2383 : : * src_pkattvals. We used to use "SELECT *" here, but it's simpler to
2384 : : * generate a result tuple that matches the table's physical structure,
2385 : : * with NULLs for any dropped columns. Otherwise we have to deal with two
2386 : : * different tupdescs and everything's very confusing.
2387 : : */
2388 : 8 : appendStringInfoString(&buf, "SELECT ");
2389 : :
2390 [ + + ]: 38 : for (i = 0; i < natts; i++)
2391 : : {
2939 andres@anarazel.de 2392 : 30 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
2393 : :
5562 tgl@sss.pgh.pa.us 2394 [ + + ]: 30 : if (i > 0)
2395 : 22 : appendStringInfoString(&buf, ", ");
2396 : :
2939 andres@anarazel.de 2397 [ + + ]: 30 : if (attr->attisdropped)
5562 tgl@sss.pgh.pa.us 2398 : 4 : appendStringInfoString(&buf, "NULL");
2399 : : else
2400 : 26 : appendStringInfoString(&buf,
2939 andres@anarazel.de 2401 : 26 : quote_ident_cstr(NameStr(attr->attname)));
2402 : : }
2403 : :
5562 tgl@sss.pgh.pa.us 2404 : 8 : appendStringInfo(&buf, " FROM %s WHERE ", relname);
2405 : :
8536 bruce@momjian.us 2406 [ + + ]: 22 : for (i = 0; i < pknumatts; i++)
2407 : : {
5562 tgl@sss.pgh.pa.us 2408 : 14 : int pkattnum = pkattnums[i];
2939 andres@anarazel.de 2409 : 14 : Form_pg_attribute attr = TupleDescAttr(tupdesc, pkattnum);
2410 : :
8536 bruce@momjian.us 2411 [ + + ]: 14 : if (i > 0)
4328 rhaas@postgresql.org 2412 : 6 : appendStringInfoString(&buf, " AND ");
2413 : :
7129 neilc@samurai.com 2414 : 14 : appendStringInfoString(&buf,
2939 andres@anarazel.de 2415 : 14 : quote_ident_cstr(NameStr(attr->attname)));
2416 : :
7232 tgl@sss.pgh.pa.us 2417 [ + - ]: 14 : if (src_pkattvals[i] != NULL)
7129 neilc@samurai.com 2418 : 14 : appendStringInfo(&buf, " = %s",
7232 tgl@sss.pgh.pa.us 2419 : 14 : quote_literal_cstr(src_pkattvals[i]));
2420 : : else
4328 rhaas@postgresql.org 2421 :UBC 0 : appendStringInfoString(&buf, " IS NULL");
2422 : : }
2423 : :
2424 : : /*
2425 : : * Retrieve the desired tuple
2426 : : */
7129 neilc@samurai.com 2427 :CBC 8 : ret = SPI_exec(buf.data, 0);
2428 : 8 : pfree(buf.data);
2429 : :
2430 : : /*
2431 : : * Only allow one qualifying tuple
2432 : : */
8536 bruce@momjian.us 2433 [ + - - + ]: 8 : if ((ret == SPI_OK_SELECT) && (SPI_processed > 1))
8080 tgl@sss.pgh.pa.us 2434 [ # # ]:UBC 0 : ereport(ERROR,
2435 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
2436 : : errmsg("source criteria matched more than one record")));
2437 : :
8536 bruce@momjian.us 2438 [ + - + - ]:CBC 8 : else if (ret == SPI_OK_SELECT && SPI_processed == 1)
2439 : : {
2440 : 8 : SPITupleTable *tuptable = SPI_tuptable;
2441 : :
2442 : 8 : tuple = SPI_copytuple(tuptable->vals[0]);
7955 mail@joeconway.com 2443 : 8 : SPI_finish();
2444 : :
8536 bruce@momjian.us 2445 : 8 : return tuple;
2446 : : }
2447 : : else
2448 : : {
2449 : : /*
2450 : : * no qualifying tuples
2451 : : */
7955 mail@joeconway.com 2452 :UBC 0 : SPI_finish();
2453 : :
8536 bruce@momjian.us 2454 : 0 : return NULL;
2455 : : }
2456 : :
2457 : : /*
2458 : : * never reached, but keep compiler quiet
2459 : : */
2460 : : return NULL;
2461 : : }
2462 : :
2463 : : /*
2464 : : * Open the relation named by relname_text, acquire specified type of lock,
2465 : : * verify we have specified permissions.
2466 : : * Caller must close rel when done with it.
2467 : : */
2468 : : static Relation
5563 tgl@sss.pgh.pa.us 2469 :CBC 21 : get_rel_from_relname(text *relname_text, LOCKMODE lockmode, AclMode aclmode)
2470 : : {
2471 : : RangeVar *relvar;
2472 : : Relation rel;
2473 : : AclResult aclresult;
2474 : :
7407 neilc@samurai.com 2475 : 21 : relvar = makeRangeVarFromNameList(textToQualifiedNameList(relname_text));
2420 andres@anarazel.de 2476 : 21 : rel = table_openrv(relvar, lockmode);
2477 : :
5563 tgl@sss.pgh.pa.us 2478 : 21 : aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
2479 : : aclmode);
2480 [ - + ]: 21 : if (aclresult != ACLCHECK_OK)
2835 peter_e@gmx.net 2481 :UBC 0 : aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
5563 tgl@sss.pgh.pa.us 2482 : 0 : RelationGetRelationName(rel));
2483 : :
5563 tgl@sss.pgh.pa.us 2484 :CBC 21 : return rel;
2485 : : }
2486 : :
2487 : : /*
2488 : : * generate_relation_name - copied from ruleutils.c
2489 : : * Compute the name to display for a relation
2490 : : *
2491 : : * The result includes all necessary quoting and schema-prefixing.
2492 : : */
2493 : : static char *
2494 : 20 : generate_relation_name(Relation rel)
2495 : : {
2496 : : char *nspname;
2497 : : char *result;
2498 : :
2499 : : /* Qualify the name if not visible in search path */
2500 [ + + ]: 20 : if (RelationIsVisible(RelationGetRelid(rel)))
8323 2501 : 15 : nspname = NULL;
2502 : : else
5563 2503 : 5 : nspname = get_namespace_name(rel->rd_rel->relnamespace);
2504 : :
2505 : 20 : result = quote_qualified_identifier(nspname, RelationGetRelationName(rel));
2506 : :
8323 2507 : 20 : return result;
2508 : : }
2509 : :
2510 : :
2511 : : static remoteConn *
8109 bruce@momjian.us 2512 : 84 : getConnectionByName(const char *name)
2513 : : {
2514 : : remoteConnHashEnt *hentry;
2515 : : char *key;
2516 : :
8069 2517 [ + + ]: 84 : if (!remoteConnHash)
2518 : 11 : remoteConnHash = createConnHash();
2519 : :
5574 itagaki.takahiro@gma 2520 : 84 : key = pstrdup(name);
5399 2521 : 84 : truncate_identifier(key, strlen(key), false);
8069 bruce@momjian.us 2522 : 84 : hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
2523 : : key, HASH_FIND, NULL);
2524 : :
100 tgl@sss.pgh.pa.us 2525 [ + + + - ]: 84 : if (hentry && hentry->rconn.conn != NULL)
2526 : 68 : return &hentry->rconn;
2527 : :
2942 peter_e@gmx.net 2528 : 16 : return NULL;
2529 : : }
2530 : :
2531 : : static HTAB *
8109 bruce@momjian.us 2532 : 12 : createConnHash(void)
2533 : : {
2534 : : HASHCTL ctl;
2535 : :
2536 : 12 : ctl.keysize = NAMEDATALEN;
2537 : 12 : ctl.entrysize = sizeof(remoteConnHashEnt);
2538 : :
1726 tgl@sss.pgh.pa.us 2539 : 12 : return hash_create("Remote Con hash", NUMCONN, &ctl,
2540 : : HASH_ELEM | HASH_STRINGS);
2541 : : }
2542 : :
2543 : : static remoteConn *
100 2544 : 10 : createNewConnection(const char *name)
2545 : : {
2546 : : remoteConnHashEnt *hentry;
2547 : : bool found;
2548 : : char *key;
2549 : :
8069 bruce@momjian.us 2550 [ + + ]: 10 : if (!remoteConnHash)
8080 tgl@sss.pgh.pa.us 2551 : 1 : remoteConnHash = createConnHash();
2552 : :
5574 itagaki.takahiro@gma 2553 : 10 : key = pstrdup(name);
2554 : 10 : truncate_identifier(key, strlen(key), true);
8109 bruce@momjian.us 2555 : 10 : hentry = (remoteConnHashEnt *) hash_search(remoteConnHash, key,
2556 : : HASH_ENTER, &found);
2557 : :
100 tgl@sss.pgh.pa.us 2558 [ + + + - ]: 10 : if (found && hentry->rconn.conn != NULL)
8080 2559 [ + - ]: 1 : ereport(ERROR,
2560 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
2561 : : errmsg("duplicate connection name")));
2562 : :
2563 : : /* New, or reusable, so initialize the rconn struct to zeroes */
100 2564 : 9 : memset(&hentry->rconn, 0, sizeof(remoteConn));
2565 : :
2566 : 9 : return &hentry->rconn;
2567 : : }
2568 : :
2569 : : static void
8109 bruce@momjian.us 2570 : 8 : deleteConnection(const char *name)
2571 : : {
2572 : : remoteConnHashEnt *hentry;
2573 : : bool found;
2574 : : char *key;
2575 : :
8069 2576 [ - + ]: 8 : if (!remoteConnHash)
8069 bruce@momjian.us 2577 :UBC 0 : remoteConnHash = createConnHash();
2578 : :
5574 itagaki.takahiro@gma 2579 :CBC 8 : key = pstrdup(name);
5399 2580 : 8 : truncate_identifier(key, strlen(key), false);
8109 bruce@momjian.us 2581 : 8 : hentry = (remoteConnHashEnt *) hash_search(remoteConnHash,
2582 : : key, HASH_REMOVE, &found);
2583 : :
8069 2584 [ - + ]: 8 : if (!hentry)
8080 tgl@sss.pgh.pa.us 2585 [ # # ]:UBC 0 : ereport(ERROR,
2586 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
2587 : : errmsg("undefined connection name")));
8109 bruce@momjian.us 2588 :CBC 8 : }
2589 : :
2590 : : /*
2591 : : * Ensure that require_auth and SCRAM keys are correctly set on connstr.
2592 : : * SCRAM keys used to pass-through are coming from the initial connection
2593 : : * from the client with the server.
2594 : : *
2595 : : * All required SCRAM options are set by dblink, so we just need to ensure
2596 : : * that these options are not overwritten by the user.
2597 : : *
2598 : : * See appendSCRAMKeysInfo and its usage for more.
2599 : : */
2600 : : bool
164 peter@eisentraut.org 2601 : 6 : dblink_connstr_has_required_scram_options(const char *connstr)
2602 : : {
2603 : : PQconninfoOption *options;
2604 : 6 : bool has_scram_server_key = false;
2605 : 6 : bool has_scram_client_key = false;
2606 : 6 : bool has_require_auth = false;
2607 : 6 : bool has_scram_keys = false;
2608 : :
2609 : 6 : options = PQconninfoParse(connstr, NULL);
2610 [ + - ]: 6 : if (options)
2611 : : {
2612 : : /*
2613 : : * Continue iterating even if we found the keys that we need to
2614 : : * validate to make sure that there is no other declaration of these
2615 : : * keys that can overwrite the first.
2616 : : */
2617 [ + + ]: 312 : for (PQconninfoOption *option = options; option->keyword != NULL; option++)
2618 : : {
2619 [ + + ]: 306 : if (strcmp(option->keyword, "require_auth") == 0)
2620 : : {
2621 [ + - + + ]: 6 : if (option->val != NULL && strcmp(option->val, "scram-sha-256") == 0)
2622 : 5 : has_require_auth = true;
2623 : : else
2624 : 1 : has_require_auth = false;
2625 : : }
2626 : :
2627 [ + + ]: 306 : if (strcmp(option->keyword, "scram_client_key") == 0)
2628 : : {
2629 [ + - + - ]: 6 : if (option->val != NULL && option->val[0] != '\0')
2630 : 6 : has_scram_client_key = true;
2631 : : else
164 peter@eisentraut.org 2632 :UBC 0 : has_scram_client_key = false;
2633 : : }
2634 : :
164 peter@eisentraut.org 2635 [ + + ]:CBC 306 : if (strcmp(option->keyword, "scram_server_key") == 0)
2636 : : {
2637 [ + - + - ]: 6 : if (option->val != NULL && option->val[0] != '\0')
2638 : 6 : has_scram_server_key = true;
2639 : : else
164 peter@eisentraut.org 2640 :UBC 0 : has_scram_server_key = false;
2641 : : }
2642 : : }
164 peter@eisentraut.org 2643 :CBC 6 : PQconninfoFree(options);
2644 : : }
2645 : :
29 2646 [ + - + - : 6 : has_scram_keys = has_scram_client_key && has_scram_server_key && MyProcPort != NULL && MyProcPort->has_scram_keys;
+ - + - ]
2647 : :
164 2648 [ + - + + ]: 6 : return (has_scram_keys && has_require_auth);
2649 : : }
2650 : :
2651 : : /*
2652 : : * We need to make sure that the connection made used credentials
2653 : : * which were provided by the user, so check what credentials were
2654 : : * used to connect and then make sure that they came from the user.
2655 : : *
2656 : : * On failure, we close "conn" and also delete the hashtable entry
2657 : : * identified by "connname" (if that's not NULL).
2658 : : */
2659 : : static void
100 tgl@sss.pgh.pa.us 2660 : 23 : dblink_security_check(PGconn *conn, const char *connname, const char *connstr)
2661 : : {
2662 : : /* Superuser bypasses security check */
877 sfrost@snowman.net 2663 [ + + ]: 23 : if (superuser())
2664 : 18 : return;
2665 : :
2666 : : /* If password was used to connect, make sure it was one provided */
2667 [ + + - + ]: 5 : if (PQconnectionUsedPassword(conn) && dblink_connstr_has_pw(connstr))
877 sfrost@snowman.net 2668 :UBC 0 : return;
2669 : :
2670 : : /*
2671 : : * Password was not used to connect, check if SCRAM pass-through is in
2672 : : * use.
2673 : : *
2674 : : * If dblink_connstr_has_required_scram_options is true we assume that
2675 : : * UseScramPassthrough is also true because the required SCRAM keys are
2676 : : * only added if UseScramPassthrough is set, and the user is not allowed
2677 : : * to add the SCRAM keys on fdw and user mapping options.
2678 : : */
29 peter@eisentraut.org 2679 [ + - + + :CBC 5 : if (MyProcPort != NULL && MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr))
+ - ]
164 2680 : 2 : return;
2681 : :
2682 : : #ifdef ENABLE_GSS
2683 : : /* If GSSAPI creds used to connect, make sure it was one delegated */
840 bruce@momjian.us 2684 [ + + + - ]: 3 : if (PQconnectionUsedGSSAPI(conn) && be_gssapi_get_delegation(MyProcPort))
877 sfrost@snowman.net 2685 : 2 : return;
2686 : : #endif
2687 : :
2688 : : /* Otherwise, fail out */
2689 : 1 : libpqsrv_disconnect(conn);
100 tgl@sss.pgh.pa.us 2690 [ - + ]: 1 : if (connname)
100 tgl@sss.pgh.pa.us 2691 :UBC 0 : deleteConnection(connname);
2692 : :
877 sfrost@snowman.net 2693 [ + - ]:CBC 1 : ereport(ERROR,
2694 : : (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2695 : : errmsg("password or GSSAPI delegated credentials required"),
2696 : : errdetail("Non-superusers may only connect using credentials they provide, eg: password in connection string or delegated GSSAPI credentials"),
2697 : : errhint("Ensure provided credentials match target server's authentication method.")));
2698 : : }
2699 : :
2700 : : /*
2701 : : * Function to check if the connection string includes an explicit
2702 : : * password, needed to ensure that non-superuser password-based auth
2703 : : * is using a provided password and not one picked up from the
2704 : : * environment.
2705 : : */
2706 : : static bool
2707 : 13 : dblink_connstr_has_pw(const char *connstr)
2708 : : {
2709 : : PQconninfoOption *options;
2710 : : PQconninfoOption *option;
2711 : 13 : bool connstr_gives_password = false;
2712 : :
2713 : 13 : options = PQconninfoParse(connstr, NULL);
2714 [ + - ]: 13 : if (options)
2715 : : {
2716 [ + + ]: 628 : for (option = options; option->keyword != NULL; option++)
2717 : : {
2718 [ + + ]: 616 : if (strcmp(option->keyword, "password") == 0)
2719 : : {
2720 [ + + + - ]: 13 : if (option->val != NULL && option->val[0] != '\0')
2721 : : {
2722 : 1 : connstr_gives_password = true;
2723 : 1 : break;
2724 : : }
2725 : : }
2726 : : }
2727 : 13 : PQconninfoFree(options);
2728 : : }
2729 : :
2730 : 13 : return connstr_gives_password;
2731 : : }
2732 : :
2733 : : /*
2734 : : * For non-superusers, insist that the connstr specify a password, except if
2735 : : * GSSAPI credentials have been delegated (and we check that they are used for
2736 : : * the connection in dblink_security_check later) or if SCRAM pass-through is
2737 : : * being used. This prevents a password or GSSAPI credentials from being
2738 : : * picked up from .pgpass, a service file, the environment, etc. We don't want
2739 : : * the postgres user's passwords or Kerberos credentials to be accessible to
2740 : : * non-superusers. In case of SCRAM pass-through insist that the connstr
2741 : : * has the required SCRAM pass-through options.
2742 : : */
2743 : : static void
2744 : 31 : dblink_connstr_check(const char *connstr)
2745 : : {
2746 [ + + ]: 31 : if (superuser())
2747 : 21 : return;
2748 : :
2749 [ + + ]: 10 : if (dblink_connstr_has_pw(connstr))
2750 : 1 : return;
2751 : :
29 peter@eisentraut.org 2752 [ + - + + : 9 : if (MyProcPort != NULL && MyProcPort->has_scram_keys && dblink_connstr_has_required_scram_options(connstr))
+ + ]
164 2753 : 3 : return;
2754 : :
2755 : : #ifdef ENABLE_GSS
840 bruce@momjian.us 2756 [ + + ]: 6 : if (be_gssapi_get_delegation(MyProcPort))
877 sfrost@snowman.net 2757 : 2 : return;
2758 : : #endif
2759 : :
2760 [ + - ]: 4 : ereport(ERROR,
2761 : : (errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
2762 : : errmsg("password or GSSAPI delegated credentials required"),
2763 : : errdetail("Non-superusers must provide a password in the connection string or send delegated GSSAPI credentials.")));
2764 : : }
2765 : :
2766 : : /*
2767 : : * Report an error received from the remote server
2768 : : *
2769 : : * res: the received error result
2770 : : * fail: true for ERROR ereport, false for NOTICE
2771 : : * fmt and following args: sprintf-style format and values for errcontext;
2772 : : * the resulting string should be worded like "while <some action>"
2773 : : *
2774 : : * If "res" is not NULL, it'll be PQclear'ed here (unless we throw error,
2775 : : * in which case memory context cleanup will clear it eventually).
2776 : : */
2777 : : static void
3180 mail@joeconway.com 2778 : 12 : dblink_res_error(PGconn *conn, const char *conname, PGresult *res,
2779 : : bool fail, const char *fmt,...)
2780 : : {
2781 : : int level;
6274 2782 : 12 : char *pg_diag_sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
43 tgl@sss.pgh.pa.us 2783 :GNC 12 : char *message_primary = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
2784 : 12 : char *message_detail = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
2785 : 12 : char *message_hint = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
2786 : 12 : char *message_context = PQresultErrorField(res, PG_DIAG_CONTEXT);
2787 : : int sqlstate;
2788 : : va_list ap;
2789 : : char dblink_context_msg[512];
2790 : :
6274 mail@joeconway.com 2791 [ + + ]:CBC 12 : if (fail)
2792 : 3 : level = ERROR;
2793 : : else
2794 : 9 : level = NOTICE;
2795 : :
2796 [ + - ]: 12 : if (pg_diag_sqlstate)
2797 : 12 : sqlstate = MAKE_SQLSTATE(pg_diag_sqlstate[0],
2798 : : pg_diag_sqlstate[1],
2799 : : pg_diag_sqlstate[2],
2800 : : pg_diag_sqlstate[3],
2801 : : pg_diag_sqlstate[4]);
2802 : : else
6274 mail@joeconway.com 2803 :UBC 0 : sqlstate = ERRCODE_CONNECTION_FAILURE;
2804 : :
2805 : : /*
2806 : : * If we don't get a message from the PGresult, try the PGconn. This is
2807 : : * needed because for connection-level failures, PQgetResult may just
2808 : : * return NULL, not a PGresult at all.
2809 : : */
3180 mail@joeconway.com 2810 [ - + ]:CBC 12 : if (message_primary == NULL)
3113 peter_e@gmx.net 2811 :UBC 0 : message_primary = pchomp(PQerrorMessage(conn));
2812 : :
2813 : : /*
2814 : : * Format the basic errcontext string. Below, we'll add on something
2815 : : * about the connection name. That's a violation of the translatability
2816 : : * guidelines about constructing error messages out of parts, but since
2817 : : * there's no translation support for dblink, there's no need to worry
2818 : : * about that (yet).
2819 : : */
2725 tgl@sss.pgh.pa.us 2820 :CBC 12 : va_start(ap, fmt);
2821 : 12 : vsnprintf(dblink_context_msg, sizeof(dblink_context_msg), fmt, ap);
2822 : 12 : va_end(ap);
2823 : :
6274 mail@joeconway.com 2824 [ + - + - : 12 : ereport(level,
+ - - + -
+ - + +
+ ]
2825 : : (errcode(sqlstate),
2826 : : (message_primary != NULL && message_primary[0] != '\0') ?
2827 : : errmsg_internal("%s", message_primary) :
2828 : : errmsg("could not obtain message string for remote error"),
2829 : : message_detail ? errdetail_internal("%s", message_detail) : 0,
2830 : : message_hint ? errhint("%s", message_hint) : 0,
2831 : : message_context ? (errcontext("%s", message_context)) : 0,
2832 : : conname ?
2833 : : (errcontext("%s on dblink connection named \"%s\"",
2834 : : dblink_context_msg, conname)) :
2835 : : (errcontext("%s on unnamed dblink connection",
2836 : : dblink_context_msg))));
43 tgl@sss.pgh.pa.us 2837 :GNC 9 : PQclear(res);
6274 mail@joeconway.com 2838 :CBC 9 : }
2839 : :
2840 : : /*
2841 : : * Obtain connection string for a foreign server
2842 : : */
2843 : : static char *
5936 2844 : 31 : get_connect_string(const char *servername)
2845 : : {
5931 bruce@momjian.us 2846 : 31 : ForeignServer *foreign_server = NULL;
2847 : : UserMapping *user_mapping;
2848 : : ListCell *cell;
2849 : : StringInfoData buf;
2850 : : ForeignDataWrapper *fdw;
2851 : : AclResult aclresult;
2852 : : char *srvname;
2853 : :
2854 : : static const PQconninfoOption *options = NULL;
2855 : :
3102 peter_e@gmx.net 2856 : 31 : initStringInfo(&buf);
2857 : :
2858 : : /*
2859 : : * Get list of valid libpq options.
2860 : : *
2861 : : * To avoid unnecessary work, we get the list once and use it throughout
2862 : : * the lifetime of this backend process. We don't need to care about
2863 : : * memory context issues, because PQconndefaults allocates with malloc.
2864 : : */
3180 mail@joeconway.com 2865 [ + + ]: 31 : if (!options)
2866 : : {
2867 : 12 : options = PQconndefaults();
2868 [ - + ]: 12 : if (!options) /* assume reason for failure is OOM */
3180 mail@joeconway.com 2869 [ # # ]:UBC 0 : ereport(ERROR,
2870 : : (errcode(ERRCODE_FDW_OUT_OF_MEMORY),
2871 : : errmsg("out of memory"),
2872 : : errdetail("Could not get libpq's default connection options.")));
2873 : : }
2874 : :
2875 : : /* first gather the server connstr options */
5574 itagaki.takahiro@gma 2876 :CBC 31 : srvname = pstrdup(servername);
5568 2877 : 31 : truncate_identifier(srvname, strlen(srvname), false);
5574 2878 : 31 : foreign_server = GetForeignServerByName(srvname, true);
2879 : :
5936 mail@joeconway.com 2880 [ + + ]: 31 : if (foreign_server)
2881 : : {
5931 bruce@momjian.us 2882 : 6 : Oid serverid = foreign_server->serverid;
2883 : 6 : Oid fdwid = foreign_server->fdwid;
2884 : 6 : Oid userid = GetUserId();
2885 : :
5936 mail@joeconway.com 2886 : 6 : user_mapping = GetUserMapping(userid, serverid);
5931 bruce@momjian.us 2887 : 6 : fdw = GetForeignDataWrapper(fdwid);
2888 : :
2889 : : /* Check permissions, user must have usage on the server. */
1028 peter@eisentraut.org 2890 : 6 : aclresult = object_aclcheck(ForeignServerRelationId, serverid, userid, ACL_USAGE);
5936 mail@joeconway.com 2891 [ - + ]: 6 : if (aclresult != ACLCHECK_OK)
2835 peter_e@gmx.net 2892 :UBC 0 : aclcheck_error(aclresult, OBJECT_FOREIGN_SERVER, foreign_server->servername);
2893 : :
2894 : : /*
2895 : : * First append hardcoded options needed for SCRAM pass-through, so if
2896 : : * the user overwrites these options we can ereport on
2897 : : * dblink_connstr_check and dblink_security_check.
2898 : : */
29 peter@eisentraut.org 2899 [ + - + + :CBC 6 : if (MyProcPort != NULL && MyProcPort->has_scram_keys && UseScramPassthrough(foreign_server, user_mapping))
+ - ]
164 2900 : 4 : appendSCRAMKeysInfo(&buf);
2901 : :
5931 bruce@momjian.us 2902 [ - + - - : 6 : foreach(cell, fdw->options)
- + ]
2903 : : {
5931 bruce@momjian.us 2904 :UBC 0 : DefElem *def = lfirst(cell);
2905 : :
3180 mail@joeconway.com 2906 [ # # ]: 0 : if (is_valid_dblink_option(options, def->defname, ForeignDataWrapperRelationId))
3102 peter_e@gmx.net 2907 : 0 : appendStringInfo(&buf, "%s='%s' ", def->defname,
3180 mail@joeconway.com 2908 : 0 : escape_param_str(strVal(def->arg)));
2909 : : }
2910 : :
5931 bruce@momjian.us 2911 [ + - + + :CBC 27 : foreach(cell, foreign_server->options)
+ + ]
2912 : : {
2913 : 21 : DefElem *def = lfirst(cell);
2914 : :
3180 mail@joeconway.com 2915 [ + + ]: 21 : if (is_valid_dblink_option(options, def->defname, ForeignServerRelationId))
3102 peter_e@gmx.net 2916 : 17 : appendStringInfo(&buf, "%s='%s' ", def->defname,
3180 mail@joeconway.com 2917 : 17 : escape_param_str(strVal(def->arg)));
2918 : : }
2919 : :
5931 bruce@momjian.us 2920 [ + - + + : 12 : foreach(cell, user_mapping->options)
+ + ]
2921 : : {
2922 : :
2923 : 6 : DefElem *def = lfirst(cell);
2924 : :
3180 mail@joeconway.com 2925 [ + - ]: 6 : if (is_valid_dblink_option(options, def->defname, UserMappingRelationId))
3102 peter_e@gmx.net 2926 : 6 : appendStringInfo(&buf, "%s='%s' ", def->defname,
3180 mail@joeconway.com 2927 : 6 : escape_param_str(strVal(def->arg)));
2928 : : }
2929 : :
3102 peter_e@gmx.net 2930 : 6 : return buf.data;
2931 : : }
2932 : : else
5936 mail@joeconway.com 2933 : 25 : return NULL;
2934 : : }
2935 : :
2936 : : /*
2937 : : * Escaping libpq connect parameter strings.
2938 : : *
2939 : : * Replaces "'" with "\'" and "\" with "\\".
2940 : : */
2941 : : static char *
2942 : 23 : escape_param_str(const char *str)
2943 : : {
2944 : : const char *cp;
2945 : : StringInfoData buf;
2946 : :
3102 peter_e@gmx.net 2947 : 23 : initStringInfo(&buf);
2948 : :
5936 mail@joeconway.com 2949 [ + + ]: 199 : for (cp = str; *cp; cp++)
2950 : : {
2951 [ + - - + ]: 176 : if (*cp == '\\' || *cp == '\'')
3102 peter_e@gmx.net 2952 :UBC 0 : appendStringInfoChar(&buf, '\\');
3102 peter_e@gmx.net 2953 :CBC 176 : appendStringInfoChar(&buf, *cp);
2954 : : }
2955 : :
2956 : 23 : return buf.data;
2957 : : }
2958 : :
2959 : : /*
2960 : : * Validate the PK-attnums argument for dblink_build_sql_insert() and related
2961 : : * functions, and translate to the internal representation.
2962 : : *
2963 : : * The user supplies an int2vector of 1-based logical attnums, plus a count
2964 : : * argument (the need for the separate count argument is historical, but we
2965 : : * still check it). We check that each attnum corresponds to a valid,
2966 : : * non-dropped attribute of the rel. We do *not* prevent attnums from being
2967 : : * listed twice, though the actual use-case for such things is dubious.
2968 : : * Note that before Postgres 9.0, the user's attnums were interpreted as
2969 : : * physical not logical column numbers; this was changed for future-proofing.
2970 : : *
2971 : : * The internal representation is a palloc'd int array of 0-based physical
2972 : : * attnums.
2973 : : */
2974 : : static void
5562 tgl@sss.pgh.pa.us 2975 : 18 : validate_pkattnums(Relation rel,
2976 : : int2vector *pkattnums_arg, int32 pknumatts_arg,
2977 : : int **pkattnums, int *pknumatts)
2978 : : {
2979 : 18 : TupleDesc tupdesc = rel->rd_att;
2980 : 18 : int natts = tupdesc->natts;
2981 : : int i;
2982 : :
2983 : : /* Don't take more array elements than there are */
2984 : 18 : pknumatts_arg = Min(pknumatts_arg, pkattnums_arg->dim1);
2985 : :
2986 : : /* Must have at least one pk attnum selected */
2987 [ - + ]: 18 : if (pknumatts_arg <= 0)
5562 tgl@sss.pgh.pa.us 2988 [ # # ]:UBC 0 : ereport(ERROR,
2989 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2990 : : errmsg("number of key attributes must be > 0")));
2991 : :
2992 : : /* Allocate output array */
1090 peter@eisentraut.org 2993 :CBC 18 : *pkattnums = palloc_array(int, pknumatts_arg);
5562 tgl@sss.pgh.pa.us 2994 : 18 : *pknumatts = pknumatts_arg;
2995 : :
2996 : : /* Validate attnums and convert to internal form */
2997 [ + + ]: 57 : for (i = 0; i < pknumatts_arg; i++)
2998 : : {
5541 bruce@momjian.us 2999 : 45 : int pkattnum = pkattnums_arg->values[i];
3000 : : int lnum;
3001 : : int j;
3002 : :
3003 : : /* Can throw error immediately if out of range */
5562 tgl@sss.pgh.pa.us 3004 [ + - + + ]: 45 : if (pkattnum <= 0 || pkattnum > natts)
3005 [ + - ]: 6 : ereport(ERROR,
3006 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3007 : : errmsg("invalid attribute number %d", pkattnum)));
3008 : :
3009 : : /* Identify which physical column has this logical number */
3010 : 39 : lnum = 0;
3011 [ + - ]: 69 : for (j = 0; j < natts; j++)
3012 : : {
3013 : : /* dropped columns don't count */
2939 andres@anarazel.de 3014 [ + + ]: 69 : if (TupleDescAttr(tupdesc, j)->attisdropped)
5562 tgl@sss.pgh.pa.us 3015 : 3 : continue;
3016 : :
3017 [ + + ]: 66 : if (++lnum == pkattnum)
3018 : 39 : break;
3019 : : }
3020 : :
3021 [ + - ]: 39 : if (j < natts)
3022 : 39 : (*pkattnums)[i] = j;
3023 : : else
5562 tgl@sss.pgh.pa.us 3024 [ # # ]:UBC 0 : ereport(ERROR,
3025 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3026 : : errmsg("invalid attribute number %d", pkattnum)));
3027 : : }
5694 mail@joeconway.com 3028 :CBC 12 : }
3029 : :
3030 : : /*
3031 : : * Check if the specified connection option is valid.
3032 : : *
3033 : : * We basically allow whatever libpq thinks is an option, with these
3034 : : * restrictions:
3035 : : * debug options: disallowed
3036 : : * "client_encoding": disallowed
3037 : : * "user": valid only in USER MAPPING options
3038 : : * secure options (eg password): valid only in USER MAPPING options
3039 : : * others: valid only in FOREIGN SERVER options
3040 : : *
3041 : : * We disallow client_encoding because it would be overridden anyway via
3042 : : * PQclientEncoding; allowing it to be specified would merely promote
3043 : : * confusion.
3044 : : */
3045 : : static bool
4714 tgl@sss.pgh.pa.us 3046 : 470 : is_valid_dblink_option(const PQconninfoOption *options, const char *option,
3047 : : Oid context)
3048 : : {
3049 : : const PQconninfoOption *opt;
3050 : :
3051 : : /* Look up the option in libpq result */
3052 [ + + ]: 11628 : for (opt = options; opt->keyword; opt++)
3053 : : {
3054 [ + + ]: 11622 : if (strcmp(opt->keyword, option) == 0)
3055 : 464 : break;
3056 : : }
3057 [ + + ]: 470 : if (opt->keyword == NULL)
3058 : 6 : return false;
3059 : :
3060 : : /* Disallow debug options (particularly "replication") */
3061 [ + + ]: 464 : if (strchr(opt->dispchar, 'D'))
3062 : 34 : return false;
3063 : :
3064 : : /* Disallow "client_encoding" */
3065 [ + + ]: 430 : if (strcmp(opt->keyword, "client_encoding") == 0)
3066 : 8 : return false;
3067 : :
3068 : : /*
3069 : : * Disallow OAuth options for now, since the builtin flow communicates on
3070 : : * stderr by default and can't cache tokens yet.
3071 : : */
130 jchampion@postgresql 3072 [ + + ]: 422 : if (strncmp(opt->keyword, "oauth_", strlen("oauth_")) == 0)
3073 : 36 : return false;
3074 : :
3075 : : /*
3076 : : * If the option is "user" or marked secure, it should be specified only
3077 : : * in USER MAPPING. Others should be specified only in SERVER.
3078 : : */
4714 tgl@sss.pgh.pa.us 3079 [ + + + + ]: 386 : if (strcmp(opt->keyword, "user") == 0 || strchr(opt->dispchar, '*'))
3080 : : {
3081 [ + + ]: 38 : if (context != UserMappingRelationId)
3082 : 9 : return false;
3083 : : }
3084 : : else
3085 : : {
3086 [ + + ]: 348 : if (context != ForeignServerRelationId)
3087 : 234 : return false;
3088 : : }
3089 : :
3090 : 143 : return true;
3091 : : }
3092 : :
3093 : : /*
3094 : : * Same as is_valid_dblink_option but also check for only dblink_fdw specific
3095 : : * options.
3096 : : */
3097 : : static bool
164 peter@eisentraut.org 3098 : 39 : is_valid_dblink_fdw_option(const PQconninfoOption *options, const char *option,
3099 : : Oid context)
3100 : : {
3101 [ + + ]: 39 : if (strcmp(option, "use_scram_passthrough") == 0)
3102 : 4 : return true;
3103 : :
3104 : 35 : return is_valid_dblink_option(options, option, context);
3105 : : }
3106 : :
3107 : : /*
3108 : : * Copy the remote session's values of GUCs that affect datatype I/O
3109 : : * and apply them locally in a new GUC nesting level. Returns the new
3110 : : * nestlevel (which is needed by restoreLocalGucs to undo the settings),
3111 : : * or -1 if no new nestlevel was needed.
3112 : : *
3113 : : * We use the equivalent of a function SET option to allow the settings to
3114 : : * persist only until the caller calls restoreLocalGucs. If an error is
3115 : : * thrown in between, guc.c will take care of undoing the settings.
3116 : : */
3117 : : static int
4551 tgl@sss.pgh.pa.us 3118 : 36 : applyRemoteGucs(PGconn *conn)
3119 : : {
3120 : : static const char *const GUCsAffectingIO[] = {
3121 : : "DateStyle",
3122 : : "IntervalStyle"
3123 : : };
3124 : :
3125 : 36 : int nestlevel = -1;
3126 : : int i;
3127 : :
3128 [ + + ]: 108 : for (i = 0; i < lengthof(GUCsAffectingIO); i++)
3129 : : {
3130 : 72 : const char *gucName = GUCsAffectingIO[i];
3131 : 72 : const char *remoteVal = PQparameterStatus(conn, gucName);
3132 : : const char *localVal;
3133 : :
3134 : : /*
3135 : : * If the remote server is pre-8.4, it won't have IntervalStyle, but
3136 : : * that's okay because its output format won't be ambiguous. So just
3137 : : * skip the GUC if we don't get a value for it. (We might eventually
3138 : : * need more complicated logic with remote-version checks here.)
3139 : : */
3140 [ - + ]: 72 : if (remoteVal == NULL)
4551 tgl@sss.pgh.pa.us 3141 :UBC 0 : continue;
3142 : :
3143 : : /*
3144 : : * Avoid GUC-setting overhead if the remote and local GUCs already
3145 : : * have the same value.
3146 : : */
4551 tgl@sss.pgh.pa.us 3147 :CBC 72 : localVal = GetConfigOption(gucName, false, false);
3148 [ - + ]: 72 : Assert(localVal != NULL);
3149 : :
3150 [ + + ]: 72 : if (strcmp(remoteVal, localVal) == 0)
3151 : 55 : continue;
3152 : :
3153 : : /* Create new GUC nest level if we didn't already */
3154 [ + + ]: 17 : if (nestlevel < 0)
3155 : 9 : nestlevel = NewGUCNestLevel();
3156 : :
3157 : : /* Apply the option (this will throw error on failure) */
3158 : 17 : (void) set_config_option(gucName, remoteVal,
3159 : : PGC_USERSET, PGC_S_SESSION,
3160 : : GUC_ACTION_SAVE, true, 0, false);
3161 : : }
3162 : :
3163 : 36 : return nestlevel;
3164 : : }
3165 : :
3166 : : /*
3167 : : * Restore local GUCs after they have been overlaid with remote settings.
3168 : : */
3169 : : static void
3170 : 37 : restoreLocalGucs(int nestlevel)
3171 : : {
3172 : : /* Do nothing if no new nestlevel was created */
3173 [ + + ]: 37 : if (nestlevel > 0)
3174 : 8 : AtEOXact_GUC(true, nestlevel);
3175 : 37 : }
3176 : :
3177 : : /*
3178 : : * Append SCRAM client key and server key information from the global
3179 : : * MyProcPort into the given StringInfo buffer.
3180 : : */
3181 : : static void
164 peter@eisentraut.org 3182 : 4 : appendSCRAMKeysInfo(StringInfo buf)
3183 : : {
3184 : : int len;
3185 : : int encoded_len;
3186 : : char *client_key;
3187 : : char *server_key;
3188 : :
3189 : 4 : len = pg_b64_enc_len(sizeof(MyProcPort->scram_ClientKey));
3190 : : /* don't forget the zero-terminator */
3191 : 4 : client_key = palloc0(len + 1);
121 heikki.linnakangas@i 3192 : 4 : encoded_len = pg_b64_encode(MyProcPort->scram_ClientKey,
3193 : : sizeof(MyProcPort->scram_ClientKey),
3194 : : client_key, len);
164 peter@eisentraut.org 3195 [ - + ]: 4 : if (encoded_len < 0)
164 peter@eisentraut.org 3196 [ # # ]:UBC 0 : elog(ERROR, "could not encode SCRAM client key");
3197 : :
164 peter@eisentraut.org 3198 :CBC 4 : len = pg_b64_enc_len(sizeof(MyProcPort->scram_ServerKey));
3199 : : /* don't forget the zero-terminator */
3200 : 4 : server_key = palloc0(len + 1);
121 heikki.linnakangas@i 3201 : 4 : encoded_len = pg_b64_encode(MyProcPort->scram_ServerKey,
3202 : : sizeof(MyProcPort->scram_ServerKey),
3203 : : server_key, len);
164 peter@eisentraut.org 3204 [ - + ]: 4 : if (encoded_len < 0)
164 peter@eisentraut.org 3205 [ # # ]:UBC 0 : elog(ERROR, "could not encode SCRAM server key");
3206 : :
164 peter@eisentraut.org 3207 :CBC 4 : appendStringInfo(buf, "scram_client_key='%s' ", client_key);
3208 : 4 : appendStringInfo(buf, "scram_server_key='%s' ", server_key);
148 drowley@postgresql.o 3209 : 4 : appendStringInfoString(buf, "require_auth='scram-sha-256' ");
3210 : :
164 peter@eisentraut.org 3211 : 4 : pfree(client_key);
3212 : 4 : pfree(server_key);
3213 : 4 : }
3214 : :
3215 : :
3216 : : static bool
3217 : 4 : UseScramPassthrough(ForeignServer *foreign_server, UserMapping *user)
3218 : : {
3219 : : ListCell *cell;
3220 : :
3221 [ + - + - : 16 : foreach(cell, foreign_server->options)
+ - ]
3222 : : {
3223 : 16 : DefElem *def = lfirst(cell);
3224 : :
3225 [ + + ]: 16 : if (strcmp(def->defname, "use_scram_passthrough") == 0)
3226 : 4 : return defGetBoolean(def);
3227 : : }
3228 : :
164 peter@eisentraut.org 3229 [ # # # # :UBC 0 : foreach(cell, user->options)
# # ]
3230 : : {
3231 : 0 : DefElem *def = (DefElem *) lfirst(cell);
3232 : :
3233 [ # # ]: 0 : if (strcmp(def->defname, "use_scram_passthrough") == 0)
3234 : 0 : return defGetBoolean(def);
3235 : : }
3236 : :
3237 : 0 : return false;
3238 : : }
|