|
16 | 16 | #include "access/xact.h" |
17 | 17 | #include "catalog/pg_user_mapping.h" |
18 | 18 | #include "commands/defrem.h" |
| 19 | +#include "funcapi.h" |
19 | 20 | #include "mb/pg_wchar.h" |
20 | 21 | #include "miscadmin.h" |
21 | 22 | #include "pgstat.h" |
22 | 23 | #include "postgres_fdw.h" |
23 | 24 | #include "storage/fd.h" |
24 | 25 | #include "storage/latch.h" |
| 26 | +#include "utils/builtins.h" |
25 | 27 | #include "utils/datetime.h" |
26 | 28 | #include "utils/hsearch.h" |
27 | 29 | #include "utils/inval.h" |
@@ -74,6 +76,11 @@ static unsigned int prep_stmt_number = 0; |
74 | 76 | /* tracks whether any work is needed in callback functions */ |
75 | 77 | static bool xact_got_connection = false; |
76 | 78 |
|
| 79 | +/* |
| 80 | + * SQL functions |
| 81 | + */ |
| 82 | +PG_FUNCTION_INFO_V1(postgres_fdw_get_connections); |
| 83 | + |
77 | 84 | /* prototypes of private functions */ |
78 | 85 | static void make_new_connection(ConnCacheEntry *entry, UserMapping *user); |
79 | 86 | static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user); |
@@ -1335,3 +1342,131 @@ exit: ; |
1335 | 1342 | *result = last_res; |
1336 | 1343 | return timed_out; |
1337 | 1344 | } |
| 1345 | + |
| 1346 | +/* |
| 1347 | + * List active foreign server connections. |
| 1348 | + * |
| 1349 | + * This function takes no input parameter and returns setof record made of |
| 1350 | + * following values: |
| 1351 | + * - server_name - server name of active connection. In case the foreign server |
| 1352 | + * is dropped but still the connection is active, then the server name will |
| 1353 | + * be NULL in output. |
| 1354 | + * - valid - true/false representing whether the connection is valid or not. |
| 1355 | + * Note that the connections can get invalidated in pgfdw_inval_callback. |
| 1356 | + * |
| 1357 | + * No records are returned when there are no cached connections at all. |
| 1358 | + */ |
| 1359 | +Datum |
| 1360 | +postgres_fdw_get_connections(PG_FUNCTION_ARGS) |
| 1361 | +{ |
| 1362 | +#define POSTGRES_FDW_GET_CONNECTIONS_COLS 2 |
| 1363 | + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
| 1364 | + TupleDesc tupdesc; |
| 1365 | + Tuplestorestate *tupstore; |
| 1366 | + MemoryContext per_query_ctx; |
| 1367 | + MemoryContext oldcontext; |
| 1368 | + HASH_SEQ_STATUS scan; |
| 1369 | + ConnCacheEntry *entry; |
| 1370 | + |
| 1371 | + /* check to see if caller supports us returning a tuplestore */ |
| 1372 | + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) |
| 1373 | + ereport(ERROR, |
| 1374 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 1375 | + errmsg("set-valued function called in context that cannot accept a set"))); |
| 1376 | + if (!(rsinfo->allowedModes & SFRM_Materialize)) |
| 1377 | + ereport(ERROR, |
| 1378 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 1379 | + errmsg("materialize mode required, but it is not allowed in this context"))); |
| 1380 | + |
| 1381 | + /* Build a tuple descriptor for our result type */ |
| 1382 | + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
| 1383 | + elog(ERROR, "return type must be a row type"); |
| 1384 | + |
| 1385 | + /* Build tuplestore to hold the result rows */ |
| 1386 | + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; |
| 1387 | + oldcontext = MemoryContextSwitchTo(per_query_ctx); |
| 1388 | + |
| 1389 | + tupstore = tuplestore_begin_heap(true, false, work_mem); |
| 1390 | + rsinfo->returnMode = SFRM_Materialize; |
| 1391 | + rsinfo->setResult = tupstore; |
| 1392 | + rsinfo->setDesc = tupdesc; |
| 1393 | + |
| 1394 | + MemoryContextSwitchTo(oldcontext); |
| 1395 | + |
| 1396 | + /* If cache doesn't exist, we return no records */ |
| 1397 | + if (!ConnectionHash) |
| 1398 | + { |
| 1399 | + /* clean up and return the tuplestore */ |
| 1400 | + tuplestore_donestoring(tupstore); |
| 1401 | + |
| 1402 | + PG_RETURN_VOID(); |
| 1403 | + } |
| 1404 | + |
| 1405 | + hash_seq_init(&scan, ConnectionHash); |
| 1406 | + while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) |
| 1407 | + { |
| 1408 | + ForeignServer *server; |
| 1409 | + Datum values[POSTGRES_FDW_GET_CONNECTIONS_COLS]; |
| 1410 | + bool nulls[POSTGRES_FDW_GET_CONNECTIONS_COLS]; |
| 1411 | + |
| 1412 | + /* We only look for open remote connections */ |
| 1413 | + if (!entry->conn) |
| 1414 | + continue; |
| 1415 | + |
| 1416 | + server = GetForeignServerExtended(entry->serverid, FSV_MISSING_OK); |
| 1417 | + |
| 1418 | + MemSet(values, 0, sizeof(values)); |
| 1419 | + MemSet(nulls, 0, sizeof(nulls)); |
| 1420 | + |
| 1421 | + /* |
| 1422 | + * The foreign server may have been dropped in current explicit |
| 1423 | + * transaction. It is not possible to drop the server from another |
| 1424 | + * session when the connection associated with it is in use in the |
| 1425 | + * current transaction, if tried so, the drop query in another session |
| 1426 | + * blocks until the current transaction finishes. |
| 1427 | + * |
| 1428 | + * Even though the server is dropped in the current transaction, the |
| 1429 | + * cache can still have associated active connection entry, say we |
| 1430 | + * call such connections dangling. Since we can not fetch the server |
| 1431 | + * name from system catalogs for dangling connections, instead we |
| 1432 | + * show NULL value for server name in output. |
| 1433 | + * |
| 1434 | + * We could have done better by storing the server name in the cache |
| 1435 | + * entry instead of server oid so that it could be used in the output. |
| 1436 | + * But the server name in each cache entry requires 64 bytes of |
| 1437 | + * memory, which is huge, when there are many cached connections and |
| 1438 | + * the use case i.e. dropping the foreign server within the explicit |
| 1439 | + * current transaction seems rare. So, we chose to show NULL value for |
| 1440 | + * server name in output. |
| 1441 | + * |
| 1442 | + * Such dangling connections get closed either in next use or at the |
| 1443 | + * end of current explicit transaction in pgfdw_xact_callback. |
| 1444 | + */ |
| 1445 | + if (!server) |
| 1446 | + { |
| 1447 | + /* |
| 1448 | + * If the server has been dropped in the current explicit |
| 1449 | + * transaction, then this entry would have been invalidated in |
| 1450 | + * pgfdw_inval_callback at the end of drop sever command. Note |
| 1451 | + * that this connection would not have been closed in |
| 1452 | + * pgfdw_inval_callback because it is still being used in the |
| 1453 | + * current explicit transaction. So, assert that here. |
| 1454 | + */ |
| 1455 | + Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated); |
| 1456 | + |
| 1457 | + /* Show null, if no server name was found */ |
| 1458 | + nulls[0] = true; |
| 1459 | + } |
| 1460 | + else |
| 1461 | + values[0] = CStringGetTextDatum(server->servername); |
| 1462 | + |
| 1463 | + values[1] = BoolGetDatum(!entry->invalidated); |
| 1464 | + |
| 1465 | + tuplestore_putvalues(tupstore, tupdesc, values, nulls); |
| 1466 | + } |
| 1467 | + |
| 1468 | + /* clean up and return the tuplestore */ |
| 1469 | + tuplestore_donestoring(tupstore); |
| 1470 | + |
| 1471 | + PG_RETURN_VOID(); |
| 1472 | +} |
0 commit comments