Thanks to visit codestin.com
Credit goes to doxygen.postgresql.org

PostgreSQL Source Code git master
subscriptioncmds.c File Reference
#include "postgres.h"
#include "access/commit_ts.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "access/twophase.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_authid_d.h"
#include "catalog/pg_database_d.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_subscription_rel.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/subscriptioncmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "pgstat.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
#include "replication/origin.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
#include "replication/worker_internal.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/pg_lsn.h"
#include "utils/syscache.h"
Include dependency graph for subscriptioncmds.c:

Go to the source code of this file.

Data Structures

struct  SubOpts
 

Macros

#define SUBOPT_CONNECT   0x00000001
 
#define SUBOPT_ENABLED   0x00000002
 
#define SUBOPT_CREATE_SLOT   0x00000004
 
#define SUBOPT_SLOT_NAME   0x00000008
 
#define SUBOPT_COPY_DATA   0x00000010
 
#define SUBOPT_SYNCHRONOUS_COMMIT   0x00000020
 
#define SUBOPT_REFRESH   0x00000040
 
#define SUBOPT_BINARY   0x00000080
 
#define SUBOPT_STREAMING   0x00000100
 
#define SUBOPT_TWOPHASE_COMMIT   0x00000200
 
#define SUBOPT_DISABLE_ON_ERR   0x00000400
 
#define SUBOPT_PASSWORD_REQUIRED   0x00000800
 
#define SUBOPT_RUN_AS_OWNER   0x00001000
 
#define SUBOPT_FAILOVER   0x00002000
 
#define SUBOPT_RETAIN_DEAD_TUPLES   0x00004000
 
#define SUBOPT_MAX_RETENTION_DURATION   0x00008000
 
#define SUBOPT_LSN   0x00010000
 
#define SUBOPT_ORIGIN   0x00020000
 
#define IsSet(val, bits)   (((val) & (bits)) == (bits))
 

Typedefs

typedef struct SubOpts SubOpts
 

Functions

static Listfetch_table_list (WalReceiverConn *wrconn, List *publications)
 
static void check_publications_origin (WalReceiverConn *wrconn, List *publications, bool copydata, bool retain_dead_tuples, char *origin, Oid *subrel_local_oids, int subrel_count, char *subname)
 
static void check_pub_dead_tuple_retention (WalReceiverConn *wrconn)
 
static void check_duplicates_in_publist (List *publist, Datum *datums)
 
static Listmerge_publications (List *oldpublist, List *newpublist, bool addpub, const char *subname)
 
static void ReportSlotConnectionError (List *rstates, Oid subid, char *slotname, char *err)
 
static void CheckAlterSubOption (Subscription *sub, const char *option, bool slot_needs_update, bool isTopLevel)
 
static void parse_subscription_options (ParseState *pstate, List *stmt_options, bits32 supported_opts, SubOpts *opts)
 
static void check_publications (WalReceiverConn *wrconn, List *publications)
 
static Datum publicationListToArray (List *publist)
 
ObjectAddress CreateSubscription (ParseState *pstate, CreateSubscriptionStmt *stmt, bool isTopLevel)
 
static void AlterSubscription_refresh (Subscription *sub, bool copy_data, List *validate_publications)
 
ObjectAddress AlterSubscription (ParseState *pstate, AlterSubscriptionStmt *stmt, bool isTopLevel)
 
void DropSubscription (DropSubscriptionStmt *stmt, bool isTopLevel)
 
void ReplicationSlotDropAtPubNode (WalReceiverConn *wrconn, char *slotname, bool missing_ok)
 
static void AlterSubscriptionOwner_internal (Relation rel, HeapTuple tup, Oid newOwnerId)
 
ObjectAddress AlterSubscriptionOwner (const char *name, Oid newOwnerId)
 
void AlterSubscriptionOwner_oid (Oid subid, Oid newOwnerId)
 
void CheckSubDeadTupleRetention (bool check_guc, bool sub_disabled, int elevel_for_sub_disabled, bool retain_dead_tuples, bool retention_active, bool max_retention_set)
 
char defGetStreamingMode (DefElem *def)
 

Macro Definition Documentation

◆ IsSet

#define IsSet (   val,
  bits 
)    (((val) & (bits)) == (bits))

Definition at line 80 of file subscriptioncmds.c.

◆ SUBOPT_BINARY

#define SUBOPT_BINARY   0x00000080

Definition at line 67 of file subscriptioncmds.c.

◆ SUBOPT_CONNECT

#define SUBOPT_CONNECT   0x00000001

Definition at line 60 of file subscriptioncmds.c.

◆ SUBOPT_COPY_DATA

#define SUBOPT_COPY_DATA   0x00000010

Definition at line 64 of file subscriptioncmds.c.

◆ SUBOPT_CREATE_SLOT

#define SUBOPT_CREATE_SLOT   0x00000004

Definition at line 62 of file subscriptioncmds.c.

◆ SUBOPT_DISABLE_ON_ERR

#define SUBOPT_DISABLE_ON_ERR   0x00000400

Definition at line 70 of file subscriptioncmds.c.

◆ SUBOPT_ENABLED

#define SUBOPT_ENABLED   0x00000002

Definition at line 61 of file subscriptioncmds.c.

◆ SUBOPT_FAILOVER

#define SUBOPT_FAILOVER   0x00002000

Definition at line 73 of file subscriptioncmds.c.

◆ SUBOPT_LSN

#define SUBOPT_LSN   0x00010000

Definition at line 76 of file subscriptioncmds.c.

◆ SUBOPT_MAX_RETENTION_DURATION

#define SUBOPT_MAX_RETENTION_DURATION   0x00008000

Definition at line 75 of file subscriptioncmds.c.

◆ SUBOPT_ORIGIN

#define SUBOPT_ORIGIN   0x00020000

Definition at line 77 of file subscriptioncmds.c.

◆ SUBOPT_PASSWORD_REQUIRED

#define SUBOPT_PASSWORD_REQUIRED   0x00000800

Definition at line 71 of file subscriptioncmds.c.

◆ SUBOPT_REFRESH

#define SUBOPT_REFRESH   0x00000040

Definition at line 66 of file subscriptioncmds.c.

◆ SUBOPT_RETAIN_DEAD_TUPLES

#define SUBOPT_RETAIN_DEAD_TUPLES   0x00004000

Definition at line 74 of file subscriptioncmds.c.

◆ SUBOPT_RUN_AS_OWNER

#define SUBOPT_RUN_AS_OWNER   0x00001000

Definition at line 72 of file subscriptioncmds.c.

◆ SUBOPT_SLOT_NAME

#define SUBOPT_SLOT_NAME   0x00000008

Definition at line 63 of file subscriptioncmds.c.

◆ SUBOPT_STREAMING

#define SUBOPT_STREAMING   0x00000100

Definition at line 68 of file subscriptioncmds.c.

◆ SUBOPT_SYNCHRONOUS_COMMIT

#define SUBOPT_SYNCHRONOUS_COMMIT   0x00000020

Definition at line 65 of file subscriptioncmds.c.

◆ SUBOPT_TWOPHASE_COMMIT

#define SUBOPT_TWOPHASE_COMMIT   0x00000200

Definition at line 69 of file subscriptioncmds.c.

Typedef Documentation

◆ SubOpts

typedef struct SubOpts SubOpts

Function Documentation

◆ AlterSubscription()

ObjectAddress AlterSubscription ( ParseState pstate,
AlterSubscriptionStmt stmt,
bool  isTopLevel 
)

Definition at line 1195 of file subscriptioncmds.c.

1197{
1198 Relation rel;
1199 ObjectAddress myself;
1200 bool nulls[Natts_pg_subscription];
1201 bool replaces[Natts_pg_subscription];
1202 Datum values[Natts_pg_subscription];
1203 HeapTuple tup;
1204 Oid subid;
1205 bool update_tuple = false;
1206 bool update_failover = false;
1207 bool update_two_phase = false;
1208 bool check_pub_rdt = false;
1209 bool retain_dead_tuples;
1210 int max_retention;
1211 bool retention_active;
1212 char *origin;
1213 Subscription *sub;
1215 bits32 supported_opts;
1216 SubOpts opts = {0};
1217
1218 rel = table_open(SubscriptionRelationId, RowExclusiveLock);
1219
1220 /* Fetch the existing tuple. */
1221 tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
1222 CStringGetDatum(stmt->subname));
1223
1224 if (!HeapTupleIsValid(tup))
1225 ereport(ERROR,
1226 (errcode(ERRCODE_UNDEFINED_OBJECT),
1227 errmsg("subscription \"%s\" does not exist",
1228 stmt->subname)));
1229
1230 form = (Form_pg_subscription) GETSTRUCT(tup);
1231 subid = form->oid;
1232
1233 /* must be owner */
1234 if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
1236 stmt->subname);
1237
1238 sub = GetSubscription(subid, false);
1239
1240 retain_dead_tuples = sub->retaindeadtuples;
1241 origin = sub->origin;
1242 max_retention = sub->maxretention;
1243 retention_active = sub->retentionactive;
1244
1245 /*
1246 * Don't allow non-superuser modification of a subscription with
1247 * password_required=false.
1248 */
1249 if (!sub->passwordrequired && !superuser())
1250 ereport(ERROR,
1251 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1252 errmsg("password_required=false is superuser-only"),
1253 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1254
1255 /* Lock the subscription so nobody else can do anything with it. */
1256 LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
1257
1258 /* Form a new tuple. */
1259 memset(values, 0, sizeof(values));
1260 memset(nulls, false, sizeof(nulls));
1261 memset(replaces, false, sizeof(replaces));
1262
1263 switch (stmt->kind)
1264 {
1266 {
1267 supported_opts = (SUBOPT_SLOT_NAME |
1276
1277 parse_subscription_options(pstate, stmt->options,
1278 supported_opts, &opts);
1279
1280 if (IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1281 {
1282 /*
1283 * The subscription must be disabled to allow slot_name as
1284 * 'none', otherwise, the apply worker will repeatedly try
1285 * to stream the data using that slot_name which neither
1286 * exists on the publisher nor the user will be allowed to
1287 * create it.
1288 */
1289 if (sub->enabled && !opts.slot_name)
1290 ereport(ERROR,
1291 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1292 errmsg("cannot set %s for enabled subscription",
1293 "slot_name = NONE")));
1294
1295 if (opts.slot_name)
1296 values[Anum_pg_subscription_subslotname - 1] =
1298 else
1299 nulls[Anum_pg_subscription_subslotname - 1] = true;
1300 replaces[Anum_pg_subscription_subslotname - 1] = true;
1301 }
1302
1303 if (opts.synchronous_commit)
1304 {
1305 values[Anum_pg_subscription_subsynccommit - 1] =
1306 CStringGetTextDatum(opts.synchronous_commit);
1307 replaces[Anum_pg_subscription_subsynccommit - 1] = true;
1308 }
1309
1310 if (IsSet(opts.specified_opts, SUBOPT_BINARY))
1311 {
1312 values[Anum_pg_subscription_subbinary - 1] =
1313 BoolGetDatum(opts.binary);
1314 replaces[Anum_pg_subscription_subbinary - 1] = true;
1315 }
1316
1317 if (IsSet(opts.specified_opts, SUBOPT_STREAMING))
1318 {
1319 values[Anum_pg_subscription_substream - 1] =
1320 CharGetDatum(opts.streaming);
1321 replaces[Anum_pg_subscription_substream - 1] = true;
1322 }
1323
1324 if (IsSet(opts.specified_opts, SUBOPT_DISABLE_ON_ERR))
1325 {
1326 values[Anum_pg_subscription_subdisableonerr - 1]
1327 = BoolGetDatum(opts.disableonerr);
1328 replaces[Anum_pg_subscription_subdisableonerr - 1]
1329 = true;
1330 }
1331
1332 if (IsSet(opts.specified_opts, SUBOPT_PASSWORD_REQUIRED))
1333 {
1334 /* Non-superuser may not disable password_required. */
1335 if (!opts.passwordrequired && !superuser())
1336 ereport(ERROR,
1337 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1338 errmsg("password_required=false is superuser-only"),
1339 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1340
1341 values[Anum_pg_subscription_subpasswordrequired - 1]
1342 = BoolGetDatum(opts.passwordrequired);
1343 replaces[Anum_pg_subscription_subpasswordrequired - 1]
1344 = true;
1345 }
1346
1347 if (IsSet(opts.specified_opts, SUBOPT_RUN_AS_OWNER))
1348 {
1349 values[Anum_pg_subscription_subrunasowner - 1] =
1350 BoolGetDatum(opts.runasowner);
1351 replaces[Anum_pg_subscription_subrunasowner - 1] = true;
1352 }
1353
1354 if (IsSet(opts.specified_opts, SUBOPT_TWOPHASE_COMMIT))
1355 {
1356 /*
1357 * We need to update both the slot and the subscription
1358 * for the two_phase option. We can enable the two_phase
1359 * option for a slot only once the initial data
1360 * synchronization is done. This is to avoid missing some
1361 * data as explained in comments atop worker.c.
1362 */
1363 update_two_phase = !opts.twophase;
1364
1365 CheckAlterSubOption(sub, "two_phase", update_two_phase,
1366 isTopLevel);
1367
1368 /*
1369 * Modifying the two_phase slot option requires a slot
1370 * lookup by slot name, so changing the slot name at the
1371 * same time is not allowed.
1372 */
1373 if (update_two_phase &&
1374 IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1375 ereport(ERROR,
1376 (errcode(ERRCODE_SYNTAX_ERROR),
1377 errmsg("\"slot_name\" and \"two_phase\" cannot be altered at the same time")));
1378
1379 /*
1380 * Note that workers may still survive even if the
1381 * subscription has been disabled.
1382 *
1383 * Ensure workers have already been exited to avoid
1384 * getting prepared transactions while we are disabling
1385 * the two_phase option. Otherwise, the changes of an
1386 * already prepared transaction can be replicated again
1387 * along with its corresponding commit, leading to
1388 * duplicate data or errors.
1389 */
1390 if (logicalrep_workers_find(subid, true, true))
1391 ereport(ERROR,
1392 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1393 errmsg("cannot alter \"two_phase\" when logical replication worker is still running"),
1394 errhint("Try again after some time.")));
1395
1396 /*
1397 * two_phase cannot be disabled if there are any
1398 * uncommitted prepared transactions present otherwise it
1399 * can lead to duplicate data or errors as explained in
1400 * the comment above.
1401 */
1402 if (update_two_phase &&
1403 sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED &&
1404 LookupGXactBySubid(subid))
1405 ereport(ERROR,
1406 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1407 errmsg("cannot disable \"two_phase\" when prepared transactions exist"),
1408 errhint("Resolve these transactions and try again.")));
1409
1410 /* Change system catalog accordingly */
1411 values[Anum_pg_subscription_subtwophasestate - 1] =
1412 CharGetDatum(opts.twophase ?
1413 LOGICALREP_TWOPHASE_STATE_PENDING :
1414 LOGICALREP_TWOPHASE_STATE_DISABLED);
1415 replaces[Anum_pg_subscription_subtwophasestate - 1] = true;
1416 }
1417
1418 if (IsSet(opts.specified_opts, SUBOPT_FAILOVER))
1419 {
1420 /*
1421 * Similar to the two_phase case above, we need to update
1422 * the failover option for both the slot and the
1423 * subscription.
1424 */
1425 update_failover = true;
1426
1427 CheckAlterSubOption(sub, "failover", update_failover,
1428 isTopLevel);
1429
1430 values[Anum_pg_subscription_subfailover - 1] =
1431 BoolGetDatum(opts.failover);
1432 replaces[Anum_pg_subscription_subfailover - 1] = true;
1433 }
1434
1435 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES))
1436 {
1437 values[Anum_pg_subscription_subretaindeadtuples - 1] =
1438 BoolGetDatum(opts.retaindeadtuples);
1439 replaces[Anum_pg_subscription_subretaindeadtuples - 1] = true;
1440
1441 /*
1442 * Update the retention status only if there's a change in
1443 * the retain_dead_tuples option value.
1444 *
1445 * Automatically marking retention as active when
1446 * retain_dead_tuples is enabled may not always be ideal,
1447 * especially if retention was previously stopped and the
1448 * user toggles retain_dead_tuples without adjusting the
1449 * publisher workload. However, this behavior provides a
1450 * convenient way for users to manually refresh the
1451 * retention status. Since retention will be stopped again
1452 * unless the publisher workload is reduced, this approach
1453 * is acceptable for now.
1454 */
1455 if (opts.retaindeadtuples != sub->retaindeadtuples)
1456 {
1457 values[Anum_pg_subscription_subretentionactive - 1] =
1458 BoolGetDatum(opts.retaindeadtuples);
1459 replaces[Anum_pg_subscription_subretentionactive - 1] = true;
1460
1461 retention_active = opts.retaindeadtuples;
1462 }
1463
1464 CheckAlterSubOption(sub, "retain_dead_tuples", false, isTopLevel);
1465
1466 /*
1467 * Workers may continue running even after the
1468 * subscription has been disabled.
1469 *
1470 * To prevent race conditions (as described in
1471 * CheckAlterSubOption()), ensure that all worker
1472 * processes have already exited before proceeding.
1473 */
1474 if (logicalrep_workers_find(subid, true, true))
1475 ereport(ERROR,
1476 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1477 errmsg("cannot alter retain_dead_tuples when logical replication worker is still running"),
1478 errhint("Try again after some time.")));
1479
1480 /*
1481 * Notify the launcher to manage the replication slot for
1482 * conflict detection. This ensures that replication slot
1483 * is efficiently handled (created, updated, or dropped)
1484 * in response to any configuration changes.
1485 */
1487
1488 check_pub_rdt = opts.retaindeadtuples;
1489 retain_dead_tuples = opts.retaindeadtuples;
1490 }
1491
1492 if (IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
1493 {
1494 values[Anum_pg_subscription_submaxretention - 1] =
1495 Int32GetDatum(opts.maxretention);
1496 replaces[Anum_pg_subscription_submaxretention - 1] = true;
1497
1498 max_retention = opts.maxretention;
1499 }
1500
1501 /*
1502 * Ensure that system configuration paramters are set
1503 * appropriately to support retain_dead_tuples and
1504 * max_retention_duration.
1505 */
1506 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES) ||
1507 IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
1509 retain_dead_tuples,
1510 retention_active,
1511 (max_retention > 0));
1512
1513 if (IsSet(opts.specified_opts, SUBOPT_ORIGIN))
1514 {
1515 values[Anum_pg_subscription_suborigin - 1] =
1516 CStringGetTextDatum(opts.origin);
1517 replaces[Anum_pg_subscription_suborigin - 1] = true;
1518
1519 /*
1520 * Check if changes from different origins may be received
1521 * from the publisher when the origin is changed to ANY
1522 * and retain_dead_tuples is enabled.
1523 */
1524 check_pub_rdt = retain_dead_tuples &&
1525 pg_strcasecmp(opts.origin, LOGICALREP_ORIGIN_ANY) == 0;
1526
1527 origin = opts.origin;
1528 }
1529
1530 update_tuple = true;
1531 break;
1532 }
1533
1535 {
1536 parse_subscription_options(pstate, stmt->options,
1538 Assert(IsSet(opts.specified_opts, SUBOPT_ENABLED));
1539
1540 if (!sub->slotname && opts.enabled)
1541 ereport(ERROR,
1542 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1543 errmsg("cannot enable subscription that does not have a slot name")));
1544
1545 /*
1546 * Check track_commit_timestamp only when enabling the
1547 * subscription in case it was disabled after creation. See
1548 * comments atop CheckSubDeadTupleRetention() for details.
1549 */
1550 CheckSubDeadTupleRetention(opts.enabled, !opts.enabled,
1552 sub->retentionactive, false);
1553
1554 values[Anum_pg_subscription_subenabled - 1] =
1555 BoolGetDatum(opts.enabled);
1556 replaces[Anum_pg_subscription_subenabled - 1] = true;
1557
1558 if (opts.enabled)
1560
1561 update_tuple = true;
1562
1563 /*
1564 * The subscription might be initially created with
1565 * connect=false and retain_dead_tuples=true, meaning the
1566 * remote server's status may not be checked. Ensure this
1567 * check is conducted now.
1568 */
1569 check_pub_rdt = sub->retaindeadtuples && opts.enabled;
1570 break;
1571 }
1572
1574 /* Load the library providing us libpq calls. */
1575 load_file("libpqwalreceiver", false);
1576 /* Check the connection info string. */
1577 walrcv_check_conninfo(stmt->conninfo,
1578 sub->passwordrequired && !sub->ownersuperuser);
1579
1580 values[Anum_pg_subscription_subconninfo - 1] =
1581 CStringGetTextDatum(stmt->conninfo);
1582 replaces[Anum_pg_subscription_subconninfo - 1] = true;
1583 update_tuple = true;
1584
1585 /*
1586 * Since the remote server configuration might have changed,
1587 * perform a check to ensure it permits enabling
1588 * retain_dead_tuples.
1589 */
1590 check_pub_rdt = sub->retaindeadtuples;
1591 break;
1592
1594 {
1595 supported_opts = SUBOPT_COPY_DATA | SUBOPT_REFRESH;
1596 parse_subscription_options(pstate, stmt->options,
1597 supported_opts, &opts);
1598
1599 values[Anum_pg_subscription_subpublications - 1] =
1600 publicationListToArray(stmt->publication);
1601 replaces[Anum_pg_subscription_subpublications - 1] = true;
1602
1603 update_tuple = true;
1604
1605 /* Refresh if user asked us to. */
1606 if (opts.refresh)
1607 {
1608 if (!sub->enabled)
1609 ereport(ERROR,
1610 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1611 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
1612 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
1613
1614 /*
1615 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
1616 * not allowed.
1617 */
1618 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
1619 ereport(ERROR,
1620 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1621 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
1622 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
1623
1624 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
1625
1626 /* Make sure refresh sees the new list of publications. */
1627 sub->publications = stmt->publication;
1628
1629 AlterSubscription_refresh(sub, opts.copy_data,
1630 stmt->publication);
1631 }
1632
1633 break;
1634 }
1635
1638 {
1639 List *publist;
1640 bool isadd = stmt->kind == ALTER_SUBSCRIPTION_ADD_PUBLICATION;
1641
1642 supported_opts = SUBOPT_REFRESH | SUBOPT_COPY_DATA;
1643 parse_subscription_options(pstate, stmt->options,
1644 supported_opts, &opts);
1645
1646 publist = merge_publications(sub->publications, stmt->publication, isadd, stmt->subname);
1647 values[Anum_pg_subscription_subpublications - 1] =
1648 publicationListToArray(publist);
1649 replaces[Anum_pg_subscription_subpublications - 1] = true;
1650
1651 update_tuple = true;
1652
1653 /* Refresh if user asked us to. */
1654 if (opts.refresh)
1655 {
1656 /* We only need to validate user specified publications. */
1657 List *validate_publications = (isadd) ? stmt->publication : NULL;
1658
1659 if (!sub->enabled)
1660 ereport(ERROR,
1661 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1662 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
1663 /* translator: %s is an SQL ALTER command */
1664 errhint("Use %s instead.",
1665 isadd ?
1666 "ALTER SUBSCRIPTION ... ADD PUBLICATION ... WITH (refresh = false)" :
1667 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
1668
1669 /*
1670 * See ALTER_SUBSCRIPTION_REFRESH for details why this is
1671 * not allowed.
1672 */
1673 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
1674 ereport(ERROR,
1675 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1676 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
1677 /* translator: %s is an SQL ALTER command */
1678 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
1679 isadd ?
1680 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
1681 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
1682
1683 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
1684
1685 /* Refresh the new list of publications. */
1686 sub->publications = publist;
1687
1688 AlterSubscription_refresh(sub, opts.copy_data,
1689 validate_publications);
1690 }
1691
1692 break;
1693 }
1694
1696 {
1697 if (!sub->enabled)
1698 ereport(ERROR,
1699 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1700 errmsg("ALTER SUBSCRIPTION ... REFRESH is not allowed for disabled subscriptions")));
1701
1702 parse_subscription_options(pstate, stmt->options,
1704
1705 /*
1706 * The subscription option "two_phase" requires that
1707 * replication has passed the initial table synchronization
1708 * phase before the two_phase becomes properly enabled.
1709 *
1710 * But, having reached this two-phase commit "enabled" state
1711 * we must not allow any subsequent table initialization to
1712 * occur. So the ALTER SUBSCRIPTION ... REFRESH is disallowed
1713 * when the user had requested two_phase = on mode.
1714 *
1715 * The exception to this restriction is when copy_data =
1716 * false, because when copy_data is false the tablesync will
1717 * start already in READY state and will exit directly without
1718 * doing anything.
1719 *
1720 * For more details see comments atop worker.c.
1721 */
1722 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
1723 ereport(ERROR,
1724 (errcode(ERRCODE_SYNTAX_ERROR),
1725 errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"),
1726 errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
1727
1728 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH");
1729
1730 AlterSubscription_refresh(sub, opts.copy_data, NULL);
1731
1732 break;
1733 }
1734
1736 {
1737 parse_subscription_options(pstate, stmt->options, SUBOPT_LSN, &opts);
1738
1739 /* ALTER SUBSCRIPTION ... SKIP supports only LSN option */
1740 Assert(IsSet(opts.specified_opts, SUBOPT_LSN));
1741
1742 /*
1743 * If the user sets subskiplsn, we do a sanity check to make
1744 * sure that the specified LSN is a probable value.
1745 */
1746 if (!XLogRecPtrIsInvalid(opts.lsn))
1747 {
1748 RepOriginId originid;
1749 char originname[NAMEDATALEN];
1750 XLogRecPtr remote_lsn;
1751
1753 originname, sizeof(originname));
1754 originid = replorigin_by_name(originname, false);
1755 remote_lsn = replorigin_get_progress(originid, false);
1756
1757 /* Check the given LSN is at least a future LSN */
1758 if (!XLogRecPtrIsInvalid(remote_lsn) && opts.lsn < remote_lsn)
1759 ereport(ERROR,
1760 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1761 errmsg("skip WAL location (LSN %X/%08X) must be greater than origin LSN %X/%08X",
1762 LSN_FORMAT_ARGS(opts.lsn),
1763 LSN_FORMAT_ARGS(remote_lsn))));
1764 }
1765
1766 values[Anum_pg_subscription_subskiplsn - 1] = LSNGetDatum(opts.lsn);
1767 replaces[Anum_pg_subscription_subskiplsn - 1] = true;
1768
1769 update_tuple = true;
1770 break;
1771 }
1772
1773 default:
1774 elog(ERROR, "unrecognized ALTER SUBSCRIPTION kind %d",
1775 stmt->kind);
1776 }
1777
1778 /* Update the catalog if needed. */
1779 if (update_tuple)
1780 {
1781 tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
1782 replaces);
1783
1784 CatalogTupleUpdate(rel, &tup->t_self, tup);
1785
1786 heap_freetuple(tup);
1787 }
1788
1789 /*
1790 * Try to acquire the connection necessary either for modifying the slot
1791 * or for checking if the remote server permits enabling
1792 * retain_dead_tuples.
1793 *
1794 * This has to be at the end because otherwise if there is an error while
1795 * doing the database operations we won't be able to rollback altered
1796 * slot.
1797 */
1798 if (update_failover || update_two_phase || check_pub_rdt)
1799 {
1800 bool must_use_password;
1801 char *err;
1803
1804 /* Load the library providing us libpq calls. */
1805 load_file("libpqwalreceiver", false);
1806
1807 /*
1808 * Try to connect to the publisher, using the new connection string if
1809 * available.
1810 */
1811 must_use_password = sub->passwordrequired && !sub->ownersuperuser;
1812 wrconn = walrcv_connect(stmt->conninfo ? stmt->conninfo : sub->conninfo,
1813 true, true, must_use_password, sub->name,
1814 &err);
1815 if (!wrconn)
1816 ereport(ERROR,
1817 (errcode(ERRCODE_CONNECTION_FAILURE),
1818 errmsg("subscription \"%s\" could not connect to the publisher: %s",
1819 sub->name, err)));
1820
1821 PG_TRY();
1822 {
1823 if (retain_dead_tuples)
1825
1827 retain_dead_tuples, origin, NULL, 0,
1828 sub->name);
1829
1830 if (update_failover || update_two_phase)
1832 update_failover ? &opts.failover : NULL,
1833 update_two_phase ? &opts.twophase : NULL);
1834 }
1835 PG_FINALLY();
1836 {
1838 }
1839 PG_END_TRY();
1840 }
1841
1843
1844 ObjectAddressSet(myself, SubscriptionRelationId, subid);
1845
1846 InvokeObjectPostAlterHook(SubscriptionRelationId, subid, 0);
1847
1848 /* Wake up related replication workers to handle this change quickly. */
1850
1851 return myself;
1852}
@ ACLCHECK_NOT_OWNER
Definition: acl.h:185
void aclcheck_error(AclResult aclerr, ObjectType objtype, const char *objectname)
Definition: aclchk.c:2652
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
Definition: aclchk.c:4088
void LogicalRepWorkersWakeupAtCommit(Oid subid)
Definition: worker.c:6214
void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid, char *originname, Size szoriginname)
Definition: worker.c:641
static Datum values[MAXATTR]
Definition: bootstrap.c:153
#define CStringGetTextDatum(s)
Definition: builtins.h:97
uint32 bits32
Definition: c.h:547
void load_file(const char *filename, bool restricted)
Definition: dfmgr.c:149
int errhint(const char *fmt,...)
Definition: elog.c:1321
int errcode(int sqlerrcode)
Definition: elog.c:854
int errmsg(const char *fmt,...)
Definition: elog.c:1071
#define PG_TRY(...)
Definition: elog.h:372
#define WARNING
Definition: elog.h:36
#define PG_END_TRY(...)
Definition: elog.h:397
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define NOTICE
Definition: elog.h:35
#define PG_FINALLY(...)
Definition: elog.h:389
#define ereport(elevel,...)
Definition: elog.h:150
void err(int eval, const char *fmt,...)
Definition: err.c:43
#define DirectFunctionCall1(func, arg1)
Definition: fmgr.h:682
Oid MyDatabaseId
Definition: globals.c:94
Assert(PointerIsAligned(start, uint64))
HeapTuple heap_modify_tuple(HeapTuple tuple, TupleDesc tupleDesc, const Datum *replValues, const bool *replIsnull, const bool *doReplace)
Definition: heaptuple.c:1210
void heap_freetuple(HeapTuple htup)
Definition: heaptuple.c:1435
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
static void * GETSTRUCT(const HeapTupleData *tuple)
Definition: htup_details.h:728
#define stmt
Definition: indent_codes.h:59
void CatalogTupleUpdate(Relation heapRel, ItemPointer otid, HeapTuple tup)
Definition: indexing.c:313
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:81
List * logicalrep_workers_find(Oid subid, bool only_running, bool acquire_lock)
Definition: launcher.c:286
void ApplyLauncherWakeupAtCommit(void)
Definition: launcher.c:1131
void LockSharedObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode)
Definition: lmgr.c:1088
#define AccessExclusiveLock
Definition: lockdefs.h:43
#define RowExclusiveLock
Definition: lockdefs.h:38
Oid GetUserId(void)
Definition: miscinit.c:469
Datum namein(PG_FUNCTION_ARGS)
Definition: name.c:48
#define InvokeObjectPostAlterHook(classId, objectId, subId)
Definition: objectaccess.h:197
#define ObjectAddressSet(addr, class_id, object_id)
Definition: objectaddress.h:40
RepOriginId replorigin_by_name(const char *roname, bool missing_ok)
Definition: origin.c:226
XLogRecPtr replorigin_get_progress(RepOriginId node, bool flush)
Definition: origin.c:1037
@ ALTER_SUBSCRIPTION_ENABLED
Definition: parsenodes.h:4346
@ ALTER_SUBSCRIPTION_DROP_PUBLICATION
Definition: parsenodes.h:4344
@ ALTER_SUBSCRIPTION_SET_PUBLICATION
Definition: parsenodes.h:4342
@ ALTER_SUBSCRIPTION_REFRESH
Definition: parsenodes.h:4345
@ ALTER_SUBSCRIPTION_SKIP
Definition: parsenodes.h:4347
@ ALTER_SUBSCRIPTION_OPTIONS
Definition: parsenodes.h:4340
@ ALTER_SUBSCRIPTION_CONNECTION
Definition: parsenodes.h:4341
@ ALTER_SUBSCRIPTION_ADD_PUBLICATION
Definition: parsenodes.h:4343
@ OBJECT_SUBSCRIPTION
Definition: parsenodes.h:2362
static AmcheckOptions opts
Definition: pg_amcheck.c:112
#define NAMEDATALEN
static Datum LSNGetDatum(XLogRecPtr X)
Definition: pg_lsn.h:31
Subscription * GetSubscription(Oid subid, bool missing_ok)
FormData_pg_subscription * Form_pg_subscription
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
static Datum BoolGetDatum(bool X)
Definition: postgres.h:112
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:262
uint64_t Datum
Definition: postgres.h:70
static Datum CStringGetDatum(const char *X)
Definition: postgres.h:360
static Datum Int32GetDatum(int32 X)
Definition: postgres.h:222
static Datum CharGetDatum(char X)
Definition: postgres.h:132
#define InvalidOid
Definition: postgres_ext.h:37
unsigned int Oid
Definition: postgres_ext.h:32
#define RelationGetDescr(relation)
Definition: rel.h:540
ItemPointerData t_self
Definition: htup.h:65
Definition: pg_list.h:54
#define SUBOPT_STREAMING
#define SUBOPT_PASSWORD_REQUIRED
#define SUBOPT_SYNCHRONOUS_COMMIT
#define SUBOPT_ENABLED
static void CheckAlterSubOption(Subscription *sub, const char *option, bool slot_needs_update, bool isTopLevel)
#define SUBOPT_RETAIN_DEAD_TUPLES
#define SUBOPT_ORIGIN
static Datum publicationListToArray(List *publist)
#define SUBOPT_FAILOVER
static void parse_subscription_options(ParseState *pstate, List *stmt_options, bits32 supported_opts, SubOpts *opts)
#define SUBOPT_RUN_AS_OWNER
#define SUBOPT_SLOT_NAME
static void check_publications_origin(WalReceiverConn *wrconn, List *publications, bool copydata, bool retain_dead_tuples, char *origin, Oid *subrel_local_oids, int subrel_count, char *subname)
#define SUBOPT_COPY_DATA
#define SUBOPT_TWOPHASE_COMMIT
static void AlterSubscription_refresh(Subscription *sub, bool copy_data, List *validate_publications)
#define SUBOPT_DISABLE_ON_ERR
void CheckSubDeadTupleRetention(bool check_guc, bool sub_disabled, int elevel_for_sub_disabled, bool retain_dead_tuples, bool retention_active, bool max_retention_set)
#define SUBOPT_LSN
#define SUBOPT_MAX_RETENTION_DURATION
static List * merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname)
#define SUBOPT_BINARY
#define IsSet(val, bits)
#define SUBOPT_REFRESH
static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn)
bool superuser(void)
Definition: superuser.c:46
#define SearchSysCacheCopy2(cacheId, key1, key2)
Definition: syscache.h:93
void table_close(Relation relation, LOCKMODE lockmode)
Definition: table.c:126
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition: table.c:40
bool LookupGXactBySubid(Oid subid)
Definition: twophase.c:2797
static WalReceiverConn * wrconn
Definition: walreceiver.c:93
#define walrcv_connect(conninfo, replication, logical, must_use_password, appname, err)
Definition: walreceiver.h:435
#define walrcv_check_conninfo(conninfo, must_use_password)
Definition: walreceiver.h:437
#define walrcv_alter_slot(conn, slotname, failover, two_phase)
Definition: walreceiver.h:461
#define walrcv_disconnect(conn)
Definition: walreceiver.h:467
void PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
Definition: xact.c:3660
#define LSN_FORMAT_ARGS(lsn)
Definition: xlogdefs.h:46
#define XLogRecPtrIsInvalid(r)
Definition: xlogdefs.h:29
uint16 RepOriginId
Definition: xlogdefs.h:68
uint64 XLogRecPtr
Definition: xlogdefs.h:21

References AccessExclusiveLock, aclcheck_error(), ACLCHECK_NOT_OWNER, ALTER_SUBSCRIPTION_ADD_PUBLICATION, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED, ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_REFRESH, ALTER_SUBSCRIPTION_SET_PUBLICATION, ALTER_SUBSCRIPTION_SKIP, AlterSubscription_refresh(), ApplyLauncherWakeupAtCommit(), Assert(), BoolGetDatum(), CatalogTupleUpdate(), CharGetDatum(), check_pub_dead_tuple_retention(), check_publications_origin(), CheckAlterSubOption(), CheckSubDeadTupleRetention(), Subscription::conninfo, CStringGetDatum(), CStringGetTextDatum, DirectFunctionCall1, elog, Subscription::enabled, ereport, err(), errcode(), errhint(), errmsg(), ERROR, GETSTRUCT(), GetSubscription(), GetUserId(), heap_freetuple(), heap_modify_tuple(), HeapTupleIsValid, if(), Int32GetDatum(), InvalidOid, InvokeObjectPostAlterHook, IsSet, load_file(), LockSharedObject(), logicalrep_workers_find(), LogicalRepWorkersWakeupAtCommit(), LookupGXactBySubid(), LSN_FORMAT_ARGS, LSNGetDatum(), Subscription::maxretention, merge_publications(), MyDatabaseId, Subscription::name, NAMEDATALEN, namein(), NOTICE, object_ownercheck(), OBJECT_SUBSCRIPTION, ObjectAddressSet, ObjectIdGetDatum(), opts, Subscription::origin, Subscription::ownersuperuser, parse_subscription_options(), Subscription::passwordrequired, PG_END_TRY, PG_FINALLY, pg_strcasecmp(), PG_TRY, PreventInTransactionBlock(), publicationListToArray(), Subscription::publications, RelationGetDescr, ReplicationOriginNameForLogicalRep(), replorigin_by_name(), replorigin_get_progress(), Subscription::retaindeadtuples, Subscription::retentionactive, RowExclusiveLock, SearchSysCacheCopy2, Subscription::slotname, stmt, SUBOPT_BINARY, SUBOPT_COPY_DATA, SUBOPT_DISABLE_ON_ERR, SUBOPT_ENABLED, SUBOPT_FAILOVER, SUBOPT_LSN, SUBOPT_MAX_RETENTION_DURATION, SUBOPT_ORIGIN, SUBOPT_PASSWORD_REQUIRED, SUBOPT_REFRESH, SUBOPT_RETAIN_DEAD_TUPLES, SUBOPT_RUN_AS_OWNER, SUBOPT_SLOT_NAME, SUBOPT_STREAMING, SUBOPT_SYNCHRONOUS_COMMIT, SUBOPT_TWOPHASE_COMMIT, superuser(), HeapTupleData::t_self, table_close(), table_open(), Subscription::twophasestate, values, walrcv_alter_slot, walrcv_check_conninfo, walrcv_connect, walrcv_disconnect, WARNING, wrconn, and XLogRecPtrIsInvalid.

Referenced by ProcessUtilitySlow().

◆ AlterSubscription_refresh()

static void AlterSubscription_refresh ( Subscription sub,
bool  copy_data,
List validate_publications 
)
static

Definition at line 878 of file subscriptioncmds.c.

880{
881 char *err;
882 List *pubrel_names;
883 List *subrel_states;
884 Oid *subrel_local_oids;
885 Oid *pubrel_local_oids;
886 ListCell *lc;
887 int off;
888 int remove_rel_len;
889 int subrel_count;
890 Relation rel = NULL;
891 typedef struct SubRemoveRels
892 {
893 Oid relid;
894 char state;
895 } SubRemoveRels;
896 SubRemoveRels *sub_remove_rels;
898 bool must_use_password;
899
900 /* Load the library providing us libpq calls. */
901 load_file("libpqwalreceiver", false);
902
903 /* Try to connect to the publisher. */
904 must_use_password = sub->passwordrequired && !sub->ownersuperuser;
905 wrconn = walrcv_connect(sub->conninfo, true, true, must_use_password,
906 sub->name, &err);
907 if (!wrconn)
909 (errcode(ERRCODE_CONNECTION_FAILURE),
910 errmsg("subscription \"%s\" could not connect to the publisher: %s",
911 sub->name, err)));
912
913 PG_TRY();
914 {
915 if (validate_publications)
916 check_publications(wrconn, validate_publications);
917
918 /* Get the table list from publisher. */
919 pubrel_names = fetch_table_list(wrconn, sub->publications);
920
921 /* Get local table list. */
922 subrel_states = GetSubscriptionRelations(sub->oid, false);
923 subrel_count = list_length(subrel_states);
924
925 /*
926 * Build qsorted array of local table oids for faster lookup. This can
927 * potentially contain all tables in the database so speed of lookup
928 * is important.
929 */
930 subrel_local_oids = palloc(subrel_count * sizeof(Oid));
931 off = 0;
932 foreach(lc, subrel_states)
933 {
935
936 subrel_local_oids[off++] = relstate->relid;
937 }
938 qsort(subrel_local_oids, subrel_count,
939 sizeof(Oid), oid_cmp);
940
942 sub->retaindeadtuples, sub->origin,
943 subrel_local_oids, subrel_count, sub->name);
944
945 /*
946 * Rels that we want to remove from subscription and drop any slots
947 * and origins corresponding to them.
948 */
949 sub_remove_rels = palloc(subrel_count * sizeof(SubRemoveRels));
950
951 /*
952 * Walk over the remote tables and try to match them to locally known
953 * tables. If the table is not known locally create a new state for
954 * it.
955 *
956 * Also builds array of local oids of remote tables for the next step.
957 */
958 off = 0;
959 pubrel_local_oids = palloc(list_length(pubrel_names) * sizeof(Oid));
960
961 foreach(lc, pubrel_names)
962 {
963 RangeVar *rv = (RangeVar *) lfirst(lc);
964 Oid relid;
965
966 relid = RangeVarGetRelid(rv, AccessShareLock, false);
967
968 /* Check for supported relkind. */
970 rv->schemaname, rv->relname);
971
972 pubrel_local_oids[off++] = relid;
973
974 if (!bsearch(&relid, subrel_local_oids,
975 subrel_count, sizeof(Oid), oid_cmp))
976 {
977 AddSubscriptionRelState(sub->oid, relid,
978 copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY,
979 InvalidXLogRecPtr, true);
981 (errmsg_internal("table \"%s.%s\" added to subscription \"%s\"",
982 rv->schemaname, rv->relname, sub->name)));
983 }
984 }
985
986 /*
987 * Next remove state for tables we should not care about anymore using
988 * the data we collected above
989 */
990 qsort(pubrel_local_oids, list_length(pubrel_names),
991 sizeof(Oid), oid_cmp);
992
993 remove_rel_len = 0;
994 for (off = 0; off < subrel_count; off++)
995 {
996 Oid relid = subrel_local_oids[off];
997
998 if (!bsearch(&relid, pubrel_local_oids,
999 list_length(pubrel_names), sizeof(Oid), oid_cmp))
1000 {
1001 char state;
1002 XLogRecPtr statelsn;
1003
1004 /*
1005 * Lock pg_subscription_rel with AccessExclusiveLock to
1006 * prevent any race conditions with the apply worker
1007 * re-launching workers at the same time this code is trying
1008 * to remove those tables.
1009 *
1010 * Even if new worker for this particular rel is restarted it
1011 * won't be able to make any progress as we hold exclusive
1012 * lock on pg_subscription_rel till the transaction end. It
1013 * will simply exit as there is no corresponding rel entry.
1014 *
1015 * This locking also ensures that the state of rels won't
1016 * change till we are done with this refresh operation.
1017 */
1018 if (!rel)
1019 rel = table_open(SubscriptionRelRelationId, AccessExclusiveLock);
1020
1021 /* Last known rel state. */
1022 state = GetSubscriptionRelState(sub->oid, relid, &statelsn);
1023
1024 sub_remove_rels[remove_rel_len].relid = relid;
1025 sub_remove_rels[remove_rel_len++].state = state;
1026
1027 RemoveSubscriptionRel(sub->oid, relid);
1028
1029 logicalrep_worker_stop(sub->oid, relid);
1030
1031 /*
1032 * For READY state, we would have already dropped the
1033 * tablesync origin.
1034 */
1035 if (state != SUBREL_STATE_READY)
1036 {
1037 char originname[NAMEDATALEN];
1038
1039 /*
1040 * Drop the tablesync's origin tracking if exists.
1041 *
1042 * It is possible that the origin is not yet created for
1043 * tablesync worker, this can happen for the states before
1044 * SUBREL_STATE_FINISHEDCOPY. The tablesync worker or
1045 * apply worker can also concurrently try to drop the
1046 * origin and by this time the origin might be already
1047 * removed. For these reasons, passing missing_ok = true.
1048 */
1049 ReplicationOriginNameForLogicalRep(sub->oid, relid, originname,
1050 sizeof(originname));
1051 replorigin_drop_by_name(originname, true, false);
1052 }
1053
1055 (errmsg_internal("table \"%s.%s\" removed from subscription \"%s\"",
1057 get_rel_name(relid),
1058 sub->name)));
1059 }
1060 }
1061
1062 /*
1063 * Drop the tablesync slots associated with removed tables. This has
1064 * to be at the end because otherwise if there is an error while doing
1065 * the database operations we won't be able to rollback dropped slots.
1066 */
1067 for (off = 0; off < remove_rel_len; off++)
1068 {
1069 if (sub_remove_rels[off].state != SUBREL_STATE_READY &&
1070 sub_remove_rels[off].state != SUBREL_STATE_SYNCDONE)
1071 {
1072 char syncslotname[NAMEDATALEN] = {0};
1073
1074 /*
1075 * For READY/SYNCDONE states we know the tablesync slot has
1076 * already been dropped by the tablesync worker.
1077 *
1078 * For other states, there is no certainty, maybe the slot
1079 * does not exist yet. Also, if we fail after removing some of
1080 * the slots, next time, it will again try to drop already
1081 * dropped slots and fail. For these reasons, we allow
1082 * missing_ok = true for the drop.
1083 */
1084 ReplicationSlotNameForTablesync(sub->oid, sub_remove_rels[off].relid,
1085 syncslotname, sizeof(syncslotname));
1086 ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
1087 }
1088 }
1089 }
1090 PG_FINALLY();
1091 {
1093 }
1094 PG_END_TRY();
1095
1096 if (rel)
1097 table_close(rel, NoLock);
1098}
int errmsg_internal(const char *fmt,...)
Definition: elog.c:1161
#define DEBUG1
Definition: elog.h:30
void CheckSubscriptionRelkind(char relkind, const char *nspname, const char *relname)
void logicalrep_worker_stop(Oid subid, Oid relid)
Definition: launcher.c:633
#define NoLock
Definition: lockdefs.h:34
#define AccessShareLock
Definition: lockdefs.h:36
char * get_rel_name(Oid relid)
Definition: lsyscache.c:2095
char get_rel_relkind(Oid relid)
Definition: lsyscache.c:2170
Oid get_rel_namespace(Oid relid)
Definition: lsyscache.c:2119
char * get_namespace_name(Oid nspid)
Definition: lsyscache.c:3533
void * palloc(Size size)
Definition: mcxt.c:1365
#define RangeVarGetRelid(relation, lockmode, missing_ok)
Definition: namespace.h:98
int oid_cmp(const void *p1, const void *p2)
Definition: oid.c:258
void replorigin_drop_by_name(const char *name, bool missing_ok, bool nowait)
Definition: origin.c:439
#define lfirst(lc)
Definition: pg_list.h:172
static int list_length(const List *l)
Definition: pg_list.h:152
List * GetSubscriptionRelations(Oid subid, bool not_ready)
void RemoveSubscriptionRel(Oid subid, Oid relid)
char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn)
void AddSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn, bool retain_lock)
#define qsort(a, b, c, d)
Definition: port.h:479
char * relname
Definition: primnodes.h:83
char * schemaname
Definition: primnodes.h:80
Definition: regguts.h:323
static void check_publications(WalReceiverConn *wrconn, List *publications)
void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok)
static List * fetch_table_list(WalReceiverConn *wrconn, List *publications)
void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot)
Definition: tablesync.c:1303
#define InvalidXLogRecPtr
Definition: xlogdefs.h:28

References AccessExclusiveLock, AccessShareLock, AddSubscriptionRelState(), check_publications(), check_publications_origin(), CheckSubscriptionRelkind(), Subscription::conninfo, DEBUG1, ereport, err(), errcode(), errmsg(), errmsg_internal(), ERROR, fetch_table_list(), get_namespace_name(), get_rel_name(), get_rel_namespace(), get_rel_relkind(), GetSubscriptionRelations(), GetSubscriptionRelState(), InvalidXLogRecPtr, lfirst, list_length(), load_file(), logicalrep_worker_stop(), Subscription::name, NAMEDATALEN, NoLock, Subscription::oid, oid_cmp(), Subscription::origin, Subscription::ownersuperuser, palloc(), Subscription::passwordrequired, PG_END_TRY, PG_FINALLY, PG_TRY, Subscription::publications, qsort, RangeVarGetRelid, SubscriptionRelState::relid, RangeVar::relname, RemoveSubscriptionRel(), ReplicationOriginNameForLogicalRep(), ReplicationSlotDropAtPubNode(), ReplicationSlotNameForTablesync(), replorigin_drop_by_name(), Subscription::retaindeadtuples, RangeVar::schemaname, table_close(), table_open(), walrcv_connect, walrcv_disconnect, and wrconn.

Referenced by AlterSubscription().

◆ AlterSubscriptionOwner()

ObjectAddress AlterSubscriptionOwner ( const char *  name,
Oid  newOwnerId 
)

Definition at line 2262 of file subscriptioncmds.c.

2263{
2264 Oid subid;
2265 HeapTuple tup;
2266 Relation rel;
2267 ObjectAddress address;
2269
2270 rel = table_open(SubscriptionRelationId, RowExclusiveLock);
2271
2272 tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
2274
2275 if (!HeapTupleIsValid(tup))
2276 ereport(ERROR,
2277 (errcode(ERRCODE_UNDEFINED_OBJECT),
2278 errmsg("subscription \"%s\" does not exist", name)));
2279
2280 form = (Form_pg_subscription) GETSTRUCT(tup);
2281 subid = form->oid;
2282
2283 AlterSubscriptionOwner_internal(rel, tup, newOwnerId);
2284
2285 ObjectAddressSet(address, SubscriptionRelationId, subid);
2286
2287 heap_freetuple(tup);
2288
2290
2291 return address;
2292}
static void AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
const char * name

References AlterSubscriptionOwner_internal(), CStringGetDatum(), ereport, errcode(), errmsg(), ERROR, GETSTRUCT(), heap_freetuple(), HeapTupleIsValid, MyDatabaseId, name, ObjectAddressSet, ObjectIdGetDatum(), RowExclusiveLock, SearchSysCacheCopy2, table_close(), and table_open().

Referenced by ExecAlterOwnerStmt().

◆ AlterSubscriptionOwner_internal()

static void AlterSubscriptionOwner_internal ( Relation  rel,
HeapTuple  tup,
Oid  newOwnerId 
)
static

Definition at line 2202 of file subscriptioncmds.c.

2203{
2205 AclResult aclresult;
2206
2207 form = (Form_pg_subscription) GETSTRUCT(tup);
2208
2209 if (form->subowner == newOwnerId)
2210 return;
2211
2212 if (!object_ownercheck(SubscriptionRelationId, form->oid, GetUserId()))
2214 NameStr(form->subname));
2215
2216 /*
2217 * Don't allow non-superuser modification of a subscription with
2218 * password_required=false.
2219 */
2220 if (!form->subpasswordrequired && !superuser())
2221 ereport(ERROR,
2222 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
2223 errmsg("password_required=false is superuser-only"),
2224 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
2225
2226 /* Must be able to become new owner */
2227 check_can_set_role(GetUserId(), newOwnerId);
2228
2229 /*
2230 * current owner must have CREATE on database
2231 *
2232 * This is consistent with how ALTER SCHEMA ... OWNER TO works, but some
2233 * other object types behave differently (e.g. you can't give a table to a
2234 * user who lacks CREATE privileges on a schema).
2235 */
2236 aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId,
2238 if (aclresult != ACLCHECK_OK)
2241
2242 form->subowner = newOwnerId;
2243 CatalogTupleUpdate(rel, &tup->t_self, tup);
2244
2245 /* Update owner dependency reference */
2246 changeDependencyOnOwner(SubscriptionRelationId,
2247 form->oid,
2248 newOwnerId);
2249
2250 InvokeObjectPostAlterHook(SubscriptionRelationId,
2251 form->oid, 0);
2252
2253 /* Wake up related background processes to handle this change quickly. */
2256}
void check_can_set_role(Oid member, Oid role)
Definition: acl.c:5341
AclResult
Definition: acl.h:182
@ ACLCHECK_OK
Definition: acl.h:183
AclResult object_aclcheck(Oid classid, Oid objectid, Oid roleid, AclMode mode)
Definition: aclchk.c:3834
#define NameStr(name)
Definition: c.h:751
char * get_database_name(Oid dbid)
Definition: lsyscache.c:1259
@ OBJECT_DATABASE
Definition: parsenodes.h:2333
#define ACL_CREATE
Definition: parsenodes.h:85
void changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
Definition: pg_shdepend.c:316

References ACL_CREATE, aclcheck_error(), ACLCHECK_NOT_OWNER, ACLCHECK_OK, ApplyLauncherWakeupAtCommit(), CatalogTupleUpdate(), changeDependencyOnOwner(), check_can_set_role(), ereport, errcode(), errhint(), errmsg(), ERROR, get_database_name(), GETSTRUCT(), GetUserId(), InvokeObjectPostAlterHook, LogicalRepWorkersWakeupAtCommit(), MyDatabaseId, NameStr, object_aclcheck(), OBJECT_DATABASE, object_ownercheck(), OBJECT_SUBSCRIPTION, superuser(), and HeapTupleData::t_self.

Referenced by AlterSubscriptionOwner(), and AlterSubscriptionOwner_oid().

◆ AlterSubscriptionOwner_oid()

void AlterSubscriptionOwner_oid ( Oid  subid,
Oid  newOwnerId 
)

Definition at line 2298 of file subscriptioncmds.c.

2299{
2300 HeapTuple tup;
2301 Relation rel;
2302
2303 rel = table_open(SubscriptionRelationId, RowExclusiveLock);
2304
2305 tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
2306
2307 if (!HeapTupleIsValid(tup))
2308 ereport(ERROR,
2309 (errcode(ERRCODE_UNDEFINED_OBJECT),
2310 errmsg("subscription with OID %u does not exist", subid)));
2311
2312 AlterSubscriptionOwner_internal(rel, tup, newOwnerId);
2313
2314 heap_freetuple(tup);
2315
2317}
#define SearchSysCacheCopy1(cacheId, key1)
Definition: syscache.h:91

References AlterSubscriptionOwner_internal(), ereport, errcode(), errmsg(), ERROR, heap_freetuple(), HeapTupleIsValid, ObjectIdGetDatum(), RowExclusiveLock, SearchSysCacheCopy1, table_close(), and table_open().

Referenced by shdepReassignOwned_Owner().

◆ check_duplicates_in_publist()

static void check_duplicates_in_publist ( List publist,
Datum datums 
)
static

Definition at line 2756 of file subscriptioncmds.c.

2757{
2758 ListCell *cell;
2759 int j = 0;
2760
2761 foreach(cell, publist)
2762 {
2763 char *name = strVal(lfirst(cell));
2764 ListCell *pcell;
2765
2766 foreach(pcell, publist)
2767 {
2768 char *pname = strVal(lfirst(pcell));
2769
2770 if (pcell == cell)
2771 break;
2772
2773 if (strcmp(name, pname) == 0)
2774 ereport(ERROR,
2776 errmsg("publication name \"%s\" used more than once",
2777 pname)));
2778 }
2779
2780 if (datums)
2781 datums[j++] = CStringGetTextDatum(name);
2782 }
2783}
int j
Definition: isn.c:78
#define ERRCODE_DUPLICATE_OBJECT
Definition: streamutil.c:30
#define strVal(v)
Definition: value.h:82

References CStringGetTextDatum, ereport, errcode(), ERRCODE_DUPLICATE_OBJECT, errmsg(), ERROR, j, lfirst, name, and strVal.

Referenced by merge_publications(), and publicationListToArray().

◆ check_pub_dead_tuple_retention()

static void check_pub_dead_tuple_retention ( WalReceiverConn wrconn)
static

Definition at line 2495 of file subscriptioncmds.c.

2496{
2497 WalRcvExecResult *res;
2498 Oid RecoveryRow[1] = {BOOLOID};
2499 TupleTableSlot *slot;
2500 bool isnull;
2501 bool remote_in_recovery;
2502
2503 if (walrcv_server_version(wrconn) < 19000)
2504 ereport(ERROR,
2505 errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2506 errmsg("cannot enable retain_dead_tuples if the publisher is running a version earlier than PostgreSQL 19"));
2507
2508 res = walrcv_exec(wrconn, "SELECT pg_is_in_recovery()", 1, RecoveryRow);
2509
2510 if (res->status != WALRCV_OK_TUPLES)
2511 ereport(ERROR,
2512 (errcode(ERRCODE_CONNECTION_FAILURE),
2513 errmsg("could not obtain recovery progress from the publisher: %s",
2514 res->err)));
2515
2517 if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
2518 elog(ERROR, "failed to fetch tuple for the recovery progress");
2519
2520 remote_in_recovery = DatumGetBool(slot_getattr(slot, 1, &isnull));
2521
2522 if (remote_in_recovery)
2523 ereport(ERROR,
2524 errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2525 errmsg("cannot enable retain_dead_tuples if the publisher is in recovery."));
2526
2528
2530}
TupleTableSlot * MakeSingleTupleTableSlot(TupleDesc tupdesc, const TupleTableSlotOps *tts_ops)
Definition: execTuples.c:1427
void ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
Definition: execTuples.c:1443
const TupleTableSlotOps TTSOpsMinimalTuple
Definition: execTuples.c:86
static bool DatumGetBool(Datum X)
Definition: postgres.h:100
Tuplestorestate * tuplestore
Definition: walreceiver.h:223
TupleDesc tupledesc
Definition: walreceiver.h:224
WalRcvExecStatus status
Definition: walreceiver.h:220
bool tuplestore_gettupleslot(Tuplestorestate *state, bool forward, bool copy, TupleTableSlot *slot)
Definition: tuplestore.c:1130
static Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
Definition: tuptable.h:399
@ WALRCV_OK_TUPLES
Definition: walreceiver.h:207
static void walrcv_clear_result(WalRcvExecResult *walres)
Definition: walreceiver.h:471
#define walrcv_server_version(conn)
Definition: walreceiver.h:447
#define walrcv_exec(conn, exec, nRetTypes, retTypes)
Definition: walreceiver.h:465

References DatumGetBool(), elog, ereport, WalRcvExecResult::err, errcode(), errmsg(), ERROR, ExecDropSingleTupleTableSlot(), MakeSingleTupleTableSlot(), slot_getattr(), WalRcvExecResult::status, TTSOpsMinimalTuple, WalRcvExecResult::tupledesc, WalRcvExecResult::tuplestore, tuplestore_gettupleslot(), walrcv_clear_result(), walrcv_exec, WALRCV_OK_TUPLES, walrcv_server_version, and wrconn.

Referenced by AlterSubscription(), and CreateSubscription().

◆ check_publications()

static void check_publications ( WalReceiverConn wrconn,
List publications 
)
static

Definition at line 474 of file subscriptioncmds.c.

475{
476 WalRcvExecResult *res;
477 StringInfo cmd;
478 TupleTableSlot *slot;
479 List *publicationsCopy = NIL;
480 Oid tableRow[1] = {TEXTOID};
481
482 cmd = makeStringInfo();
483 appendStringInfoString(cmd, "SELECT t.pubname FROM\n"
484 " pg_catalog.pg_publication t WHERE\n"
485 " t.pubname IN (");
486 GetPublicationsStr(publications, cmd, true);
487 appendStringInfoChar(cmd, ')');
488
489 res = walrcv_exec(wrconn, cmd->data, 1, tableRow);
491
492 if (res->status != WALRCV_OK_TUPLES)
494 errmsg("could not receive list of publications from the publisher: %s",
495 res->err));
496
497 publicationsCopy = list_copy(publications);
498
499 /* Process publication(s). */
501 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
502 {
503 char *pubname;
504 bool isnull;
505
506 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
507 Assert(!isnull);
508
509 /* Delete the publication present in publisher from the list. */
510 publicationsCopy = list_delete(publicationsCopy, makeString(pubname));
511 ExecClearTuple(slot);
512 }
513
515
517
518 if (list_length(publicationsCopy))
519 {
520 /* Prepare the list of non-existent publication(s) for error message. */
521 StringInfo pubnames = makeStringInfo();
522
523 GetPublicationsStr(publicationsCopy, pubnames, false);
525 errcode(ERRCODE_UNDEFINED_OBJECT),
526 errmsg_plural("publication %s does not exist on the publisher",
527 "publications %s do not exist on the publisher",
528 list_length(publicationsCopy),
529 pubnames->data));
530 }
531}
#define TextDatumGetCString(d)
Definition: builtins.h:98
int errmsg_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...)
Definition: elog.c:1184
List * list_delete(List *list, void *datum)
Definition: list.c:853
List * list_copy(const List *oldlist)
Definition: list.c:1573
#define NIL
Definition: pg_list.h:68
void GetPublicationsStr(List *publications, StringInfo dest, bool quote_literal)
void destroyStringInfo(StringInfo str)
Definition: stringinfo.c:409
StringInfo makeStringInfo(void)
Definition: stringinfo.c:72
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition: stringinfo.c:242
static TupleTableSlot * ExecClearTuple(TupleTableSlot *slot)
Definition: tuptable.h:458
String * makeString(char *str)
Definition: value.c:63

References appendStringInfoChar(), appendStringInfoString(), Assert(), StringInfoData::data, destroyStringInfo(), ereport, WalRcvExecResult::err, errcode(), errmsg(), errmsg_plural(), ERROR, ExecClearTuple(), ExecDropSingleTupleTableSlot(), GetPublicationsStr(), list_copy(), list_delete(), list_length(), MakeSingleTupleTableSlot(), makeString(), makeStringInfo(), NIL, slot_getattr(), WalRcvExecResult::status, TextDatumGetCString, TTSOpsMinimalTuple, WalRcvExecResult::tupledesc, WalRcvExecResult::tuplestore, tuplestore_gettupleslot(), walrcv_clear_result(), walrcv_exec, WALRCV_OK_TUPLES, WARNING, and wrconn.

Referenced by AlterSubscription_refresh(), and CreateSubscription().

◆ check_publications_origin()

static void check_publications_origin ( WalReceiverConn wrconn,
List publications,
bool  copydata,
bool  retain_dead_tuples,
char *  origin,
Oid subrel_local_oids,
int  subrel_count,
char *  subname 
)
static

Definition at line 2343 of file subscriptioncmds.c.

2347{
2348 WalRcvExecResult *res;
2349 StringInfoData cmd;
2350 TupleTableSlot *slot;
2351 Oid tableRow[1] = {TEXTOID};
2352 List *publist = NIL;
2353 int i;
2354 bool check_rdt;
2355 bool check_table_sync;
2356 bool origin_none = origin &&
2357 pg_strcasecmp(origin, LOGICALREP_ORIGIN_NONE) == 0;
2358
2359 /*
2360 * Enable retain_dead_tuples checks only when origin is set to 'any',
2361 * since with origin='none' only local changes are replicated to the
2362 * subscriber.
2363 */
2364 check_rdt = retain_dead_tuples && !origin_none;
2365
2366 /*
2367 * Enable table synchronization checks only when origin is 'none', to
2368 * ensure that data from other origins is not inadvertently copied.
2369 */
2370 check_table_sync = copydata && origin_none;
2371
2372 /* retain_dead_tuples and table sync checks occur separately */
2373 Assert(!(check_rdt && check_table_sync));
2374
2375 /* Return if no checks are required */
2376 if (!check_rdt && !check_table_sync)
2377 return;
2378
2379 initStringInfo(&cmd);
2381 "SELECT DISTINCT P.pubname AS pubname\n"
2382 "FROM pg_publication P,\n"
2383 " LATERAL pg_get_publication_tables(P.pubname) GPT\n"
2384 " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR"
2385 " GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION"
2386 " SELECT relid FROM pg_partition_tree(PS.srrelid))),\n"
2387 " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
2388 "WHERE C.oid = GPT.relid AND P.pubname IN (");
2389 GetPublicationsStr(publications, &cmd, true);
2390 appendStringInfoString(&cmd, ")\n");
2391
2392 /*
2393 * In case of ALTER SUBSCRIPTION ... REFRESH, subrel_local_oids contains
2394 * the list of relation oids that are already present on the subscriber.
2395 * This check should be skipped for these tables if checking for table
2396 * sync scenario. However, when handling the retain_dead_tuples scenario,
2397 * ensure all tables are checked, as some existing tables may now include
2398 * changes from other origins due to newly created subscriptions on the
2399 * publisher.
2400 */
2401 if (check_table_sync)
2402 {
2403 for (i = 0; i < subrel_count; i++)
2404 {
2405 Oid relid = subrel_local_oids[i];
2406 char *schemaname = get_namespace_name(get_rel_namespace(relid));
2407 char *tablename = get_rel_name(relid);
2408
2409 appendStringInfo(&cmd, "AND NOT (N.nspname = '%s' AND C.relname = '%s')\n",
2410 schemaname, tablename);
2411 }
2412 }
2413
2414 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
2415 pfree(cmd.data);
2416
2417 if (res->status != WALRCV_OK_TUPLES)
2418 ereport(ERROR,
2419 (errcode(ERRCODE_CONNECTION_FAILURE),
2420 errmsg("could not receive list of replicated tables from the publisher: %s",
2421 res->err)));
2422
2423 /* Process tables. */
2425 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
2426 {
2427 char *pubname;
2428 bool isnull;
2429
2430 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
2431 Assert(!isnull);
2432
2433 ExecClearTuple(slot);
2434 publist = list_append_unique(publist, makeString(pubname));
2435 }
2436
2437 /*
2438 * Log a warning if the publisher has subscribed to the same table from
2439 * some other publisher. We cannot know the origin of data during the
2440 * initial sync. Data origins can be found only from the WAL by looking at
2441 * the origin id.
2442 *
2443 * XXX: For simplicity, we don't check whether the table has any data or
2444 * not. If the table doesn't have any data then we don't need to
2445 * distinguish between data having origin and data not having origin so we
2446 * can avoid logging a warning for table sync scenario.
2447 */
2448 if (publist)
2449 {
2450 StringInfo pubnames = makeStringInfo();
2451 StringInfo err_msg = makeStringInfo();
2452 StringInfo err_hint = makeStringInfo();
2453
2454 /* Prepare the list of publication(s) for warning message. */
2455 GetPublicationsStr(publist, pubnames, false);
2456
2457 if (check_table_sync)
2458 {
2459 appendStringInfo(err_msg, _("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin"),
2460 subname);
2461 appendStringInfoString(err_hint, _("Verify that initial data copied from the publisher tables did not come from other origins."));
2462 }
2463 else
2464 {
2465 appendStringInfo(err_msg, _("subscription \"%s\" enabled retain_dead_tuples but might not reliably detect conflicts for changes from different origins"),
2466 subname);
2467 appendStringInfoString(err_hint, _("Consider using origin = NONE or disabling retain_dead_tuples."));
2468 }
2469
2471 errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2472 errmsg_internal("%s", err_msg->data),
2473 errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
2474 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
2475 list_length(publist), pubnames->data),
2476 errhint_internal("%s", err_hint->data));
2477 }
2478
2480
2482}
int errhint_internal(const char *fmt,...)
Definition: elog.c:1343
int errdetail_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...)
Definition: elog.c:1299
#define _(x)
Definition: elog.c:91
int i
Definition: isn.c:77
List * list_append_unique(List *list, void *datum)
Definition: list.c:1343
void pfree(void *pointer)
Definition: mcxt.c:1594
NameData subname
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition: stringinfo.c:145
void initStringInfo(StringInfo str)
Definition: stringinfo.c:97

References _, appendStringInfo(), appendStringInfoString(), Assert(), StringInfoData::data, ereport, WalRcvExecResult::err, errcode(), errdetail_plural(), errhint_internal(), errmsg(), errmsg_internal(), ERROR, ExecClearTuple(), ExecDropSingleTupleTableSlot(), get_namespace_name(), get_rel_name(), get_rel_namespace(), GetPublicationsStr(), i, initStringInfo(), list_append_unique(), list_length(), MakeSingleTupleTableSlot(), makeString(), makeStringInfo(), NIL, pfree(), pg_strcasecmp(), slot_getattr(), WalRcvExecResult::status, subname, TextDatumGetCString, TTSOpsMinimalTuple, WalRcvExecResult::tupledesc, WalRcvExecResult::tuplestore, tuplestore_gettupleslot(), walrcv_clear_result(), walrcv_exec, WALRCV_OK_TUPLES, WARNING, and wrconn.

Referenced by AlterSubscription(), AlterSubscription_refresh(), and CreateSubscription().

◆ CheckAlterSubOption()

static void CheckAlterSubOption ( Subscription sub,
const char *  option,
bool  slot_needs_update,
bool  isTopLevel 
)
static

Definition at line 1105 of file subscriptioncmds.c.

1107{
1108 Assert(strcmp(option, "failover") == 0 ||
1109 strcmp(option, "two_phase") == 0 ||
1110 strcmp(option, "retain_dead_tuples") == 0);
1111
1112 /*
1113 * Altering the retain_dead_tuples option does not update the slot on the
1114 * publisher.
1115 */
1116 Assert(!slot_needs_update || strcmp(option, "retain_dead_tuples") != 0);
1117
1118 /*
1119 * Do not allow changing the option if the subscription is enabled. This
1120 * is because both failover and two_phase options of the slot on the
1121 * publisher cannot be modified if the slot is currently acquired by the
1122 * existing walsender.
1123 *
1124 * Note that two_phase is enabled (aka changed from 'false' to 'true') on
1125 * the publisher by the existing walsender, so we could have allowed that
1126 * even when the subscription is enabled. But we kept this restriction for
1127 * the sake of consistency and simplicity.
1128 *
1129 * Additionally, do not allow changing the retain_dead_tuples option when
1130 * the subscription is enabled to prevent race conditions arising from the
1131 * new option value being acknowledged asynchronously by the launcher and
1132 * apply workers.
1133 *
1134 * Without the restriction, a race condition may arise when a user
1135 * disables and immediately re-enables the retain_dead_tuples option. In
1136 * this case, the launcher might drop the slot upon noticing the disabled
1137 * action, while the apply worker may keep maintaining
1138 * oldest_nonremovable_xid without noticing the option change. During this
1139 * period, a transaction ID wraparound could falsely make this ID appear
1140 * as if it originates from the future w.r.t the transaction ID stored in
1141 * the slot maintained by launcher.
1142 *
1143 * Similarly, if the user enables retain_dead_tuples concurrently with the
1144 * launcher starting the worker, the apply worker may start calculating
1145 * oldest_nonremovable_xid before the launcher notices the enable action.
1146 * Consequently, the launcher may update slot.xmin to a newer value than
1147 * that maintained by the worker. In subsequent cycles, upon integrating
1148 * the worker's oldest_nonremovable_xid, the launcher might detect a
1149 * retreat in the calculated xmin, necessitating additional handling.
1150 *
1151 * XXX To address the above race conditions, we can define
1152 * oldest_nonremovable_xid as FullTransactionID and adds the check to
1153 * disallow retreating the conflict slot's xmin. For now, we kept the
1154 * implementation simple by disallowing change to the retain_dead_tuples,
1155 * but in the future we can change this after some more analysis.
1156 *
1157 * Note that we could restrict only the enabling of retain_dead_tuples to
1158 * avoid the race conditions described above, but we maintain the
1159 * restriction for both enable and disable operations for the sake of
1160 * consistency.
1161 */
1162 if (sub->enabled)
1163 ereport(ERROR,
1164 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1165 errmsg("cannot set option \"%s\" for enabled subscription",
1166 option)));
1167
1168 if (slot_needs_update)
1169 {
1170 StringInfoData cmd;
1171
1172 /*
1173 * A valid slot must be associated with the subscription for us to
1174 * modify any of the slot's properties.
1175 */
1176 if (!sub->slotname)
1177 ereport(ERROR,
1178 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1179 errmsg("cannot set option \"%s\" for a subscription that does not have a slot name",
1180 option)));
1181
1182 /* The changed option of the slot can't be rolled back. */
1183 initStringInfo(&cmd);
1184 appendStringInfo(&cmd, "ALTER SUBSCRIPTION ... SET (%s)", option);
1185
1186 PreventInTransactionBlock(isTopLevel, cmd.data);
1187 pfree(cmd.data);
1188 }
1189}

References appendStringInfo(), Assert(), StringInfoData::data, Subscription::enabled, ereport, errcode(), errmsg(), ERROR, initStringInfo(), pfree(), PreventInTransactionBlock(), and Subscription::slotname.

Referenced by AlterSubscription().

◆ CheckSubDeadTupleRetention()

void CheckSubDeadTupleRetention ( bool  check_guc,
bool  sub_disabled,
int  elevel_for_sub_disabled,
bool  retain_dead_tuples,
bool  retention_active,
bool  max_retention_set 
)

Definition at line 2556 of file subscriptioncmds.c.

2560{
2561 Assert(elevel_for_sub_disabled == NOTICE ||
2562 elevel_for_sub_disabled == WARNING);
2563
2564 if (retain_dead_tuples)
2565 {
2566 if (check_guc && wal_level < WAL_LEVEL_REPLICA)
2567 ereport(ERROR,
2568 errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2569 errmsg("\"wal_level\" is insufficient to create the replication slot required by retain_dead_tuples"),
2570 errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start."));
2571
2572 if (check_guc && !track_commit_timestamp)
2574 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2575 errmsg("commit timestamp and origin data required for detecting conflicts won't be retained"),
2576 errhint("Consider setting \"%s\" to true.",
2577 "track_commit_timestamp"));
2578
2579 if (sub_disabled && retention_active)
2580 ereport(elevel_for_sub_disabled,
2581 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2582 errmsg("deleted rows to detect conflicts would not be removed until the subscription is enabled"),
2583 (elevel_for_sub_disabled > NOTICE)
2584 ? errhint("Consider setting %s to false.",
2585 "retain_dead_tuples") : 0);
2586 }
2587 else if (max_retention_set)
2588 {
2590 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
2591 errmsg("max_retention_duration is ineffective when retain_dead_tuples is disabled"));
2592 }
2593}
bool track_commit_timestamp
Definition: commit_ts.c:109
int wal_level
Definition: xlog.c:132
@ WAL_LEVEL_REPLICA
Definition: xlog.h:75

References Assert(), ereport, errcode(), errhint(), errmsg(), ERROR, NOTICE, track_commit_timestamp, wal_level, WAL_LEVEL_REPLICA, and WARNING.

Referenced by AlterSubscription(), CreateSubscription(), and DisableSubscriptionAndExit().

◆ CreateSubscription()

ObjectAddress CreateSubscription ( ParseState pstate,
CreateSubscriptionStmt stmt,
bool  isTopLevel 
)

Definition at line 567 of file subscriptioncmds.c.

569{
570 Relation rel;
571 ObjectAddress myself;
572 Oid subid;
573 bool nulls[Natts_pg_subscription];
574 Datum values[Natts_pg_subscription];
575 Oid owner = GetUserId();
576 HeapTuple tup;
577 char *conninfo;
578 char originname[NAMEDATALEN];
579 List *publications;
580 bits32 supported_opts;
581 SubOpts opts = {0};
582 AclResult aclresult;
583
584 /*
585 * Parse and check options.
586 *
587 * Connection and publication should not be specified here.
588 */
589 supported_opts = (SUBOPT_CONNECT | SUBOPT_ENABLED | SUBOPT_CREATE_SLOT |
597 parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
598
599 /*
600 * Since creating a replication slot is not transactional, rolling back
601 * the transaction leaves the created replication slot. So we cannot run
602 * CREATE SUBSCRIPTION inside a transaction block if creating a
603 * replication slot.
604 */
605 if (opts.create_slot)
606 PreventInTransactionBlock(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
607
608 /*
609 * We don't want to allow unprivileged users to be able to trigger
610 * attempts to access arbitrary network destinations, so require the user
611 * to have been specifically authorized to create subscriptions.
612 */
613 if (!has_privs_of_role(owner, ROLE_PG_CREATE_SUBSCRIPTION))
615 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
616 errmsg("permission denied to create subscription"),
617 errdetail("Only roles with privileges of the \"%s\" role may create subscriptions.",
618 "pg_create_subscription")));
619
620 /*
621 * Since a subscription is a database object, we also check for CREATE
622 * permission on the database.
623 */
624 aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId,
625 owner, ACL_CREATE);
626 if (aclresult != ACLCHECK_OK)
629
630 /*
631 * Non-superusers are required to set a password for authentication, and
632 * that password must be used by the target server, but the superuser can
633 * exempt a subscription from this requirement.
634 */
635 if (!opts.passwordrequired && !superuser_arg(owner))
637 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
638 errmsg("password_required=false is superuser-only"),
639 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
640
641 /*
642 * If built with appropriate switch, whine when regression-testing
643 * conventions for subscription names are violated.
644 */
645#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
646 if (strncmp(stmt->subname, "regress_", 8) != 0)
647 elog(WARNING, "subscriptions created by regression test cases should have names starting with \"regress_\"");
648#endif
649
650 rel = table_open(SubscriptionRelationId, RowExclusiveLock);
651
652 /* Check if name is used */
653 subid = GetSysCacheOid2(SUBSCRIPTIONNAME, Anum_pg_subscription_oid,
655 if (OidIsValid(subid))
656 {
659 errmsg("subscription \"%s\" already exists",
660 stmt->subname)));
661 }
662
663 /*
664 * Ensure that system configuration paramters are set appropriately to
665 * support retain_dead_tuples and max_retention_duration.
666 */
668 opts.retaindeadtuples, opts.retaindeadtuples,
669 (opts.maxretention > 0));
670
671 if (!IsSet(opts.specified_opts, SUBOPT_SLOT_NAME) &&
672 opts.slot_name == NULL)
673 opts.slot_name = stmt->subname;
674
675 /* The default for synchronous_commit of subscriptions is off. */
676 if (opts.synchronous_commit == NULL)
677 opts.synchronous_commit = "off";
678
679 conninfo = stmt->conninfo;
680 publications = stmt->publication;
681
682 /* Load the library providing us libpq calls. */
683 load_file("libpqwalreceiver", false);
684
685 /* Check the connection info string. */
686 walrcv_check_conninfo(conninfo, opts.passwordrequired && !superuser());
687
688 /* Everything ok, form a new tuple. */
689 memset(values, 0, sizeof(values));
690 memset(nulls, false, sizeof(nulls));
691
692 subid = GetNewOidWithIndex(rel, SubscriptionObjectIndexId,
693 Anum_pg_subscription_oid);
694 values[Anum_pg_subscription_oid - 1] = ObjectIdGetDatum(subid);
695 values[Anum_pg_subscription_subdbid - 1] = ObjectIdGetDatum(MyDatabaseId);
696 values[Anum_pg_subscription_subskiplsn - 1] = LSNGetDatum(InvalidXLogRecPtr);
697 values[Anum_pg_subscription_subname - 1] =
699 values[Anum_pg_subscription_subowner - 1] = ObjectIdGetDatum(owner);
700 values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(opts.enabled);
701 values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(opts.binary);
702 values[Anum_pg_subscription_substream - 1] = CharGetDatum(opts.streaming);
703 values[Anum_pg_subscription_subtwophasestate - 1] =
704 CharGetDatum(opts.twophase ?
705 LOGICALREP_TWOPHASE_STATE_PENDING :
706 LOGICALREP_TWOPHASE_STATE_DISABLED);
707 values[Anum_pg_subscription_subdisableonerr - 1] = BoolGetDatum(opts.disableonerr);
708 values[Anum_pg_subscription_subpasswordrequired - 1] = BoolGetDatum(opts.passwordrequired);
709 values[Anum_pg_subscription_subrunasowner - 1] = BoolGetDatum(opts.runasowner);
710 values[Anum_pg_subscription_subfailover - 1] = BoolGetDatum(opts.failover);
711 values[Anum_pg_subscription_subretaindeadtuples - 1] =
712 BoolGetDatum(opts.retaindeadtuples);
713 values[Anum_pg_subscription_submaxretention - 1] =
714 Int32GetDatum(opts.maxretention);
715 values[Anum_pg_subscription_subretentionactive - 1] =
716 Int32GetDatum(opts.retaindeadtuples);
717 values[Anum_pg_subscription_subconninfo - 1] =
718 CStringGetTextDatum(conninfo);
719 if (opts.slot_name)
720 values[Anum_pg_subscription_subslotname - 1] =
722 else
723 nulls[Anum_pg_subscription_subslotname - 1] = true;
724 values[Anum_pg_subscription_subsynccommit - 1] =
725 CStringGetTextDatum(opts.synchronous_commit);
726 values[Anum_pg_subscription_subpublications - 1] =
727 publicationListToArray(publications);
728 values[Anum_pg_subscription_suborigin - 1] =
730
731 tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
732
733 /* Insert tuple into catalog. */
734 CatalogTupleInsert(rel, tup);
735 heap_freetuple(tup);
736
737 recordDependencyOnOwner(SubscriptionRelationId, subid, owner);
738
739 ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
740 replorigin_create(originname);
741
742 /*
743 * Connect to remote side to execute requested commands and fetch table
744 * info.
745 */
746 if (opts.connect)
747 {
748 char *err;
750 List *tables;
751 ListCell *lc;
752 char table_state;
753 bool must_use_password;
754
755 /* Try to connect to the publisher. */
756 must_use_password = !superuser_arg(owner) && opts.passwordrequired;
757 wrconn = walrcv_connect(conninfo, true, true, must_use_password,
758 stmt->subname, &err);
759 if (!wrconn)
761 (errcode(ERRCODE_CONNECTION_FAILURE),
762 errmsg("subscription \"%s\" could not connect to the publisher: %s",
763 stmt->subname, err)));
764
765 PG_TRY();
766 {
767 check_publications(wrconn, publications);
768 check_publications_origin(wrconn, publications, opts.copy_data,
769 opts.retaindeadtuples, opts.origin,
770 NULL, 0, stmt->subname);
771
772 if (opts.retaindeadtuples)
774
775 /*
776 * Set sync state based on if we were asked to do data copy or
777 * not.
778 */
779 table_state = opts.copy_data ? SUBREL_STATE_INIT : SUBREL_STATE_READY;
780
781 /*
782 * Get the table list from publisher and build local table status
783 * info.
784 */
785 tables = fetch_table_list(wrconn, publications);
786 foreach(lc, tables)
787 {
788 RangeVar *rv = (RangeVar *) lfirst(lc);
789 Oid relid;
790
791 relid = RangeVarGetRelid(rv, AccessShareLock, false);
792
793 /* Check for supported relkind. */
795 rv->schemaname, rv->relname);
796
797 AddSubscriptionRelState(subid, relid, table_state,
798 InvalidXLogRecPtr, true);
799 }
800
801 /*
802 * If requested, create permanent slot for the subscription. We
803 * won't use the initial snapshot for anything, so no need to
804 * export it.
805 */
806 if (opts.create_slot)
807 {
808 bool twophase_enabled = false;
809
810 Assert(opts.slot_name);
811
812 /*
813 * Even if two_phase is set, don't create the slot with
814 * two-phase enabled. Will enable it once all the tables are
815 * synced and ready. This avoids race-conditions like prepared
816 * transactions being skipped due to changes not being applied
817 * due to checks in should_apply_changes_for_rel() when
818 * tablesync for the corresponding tables are in progress. See
819 * comments atop worker.c.
820 *
821 * Note that if tables were specified but copy_data is false
822 * then it is safe to enable two_phase up-front because those
823 * tables are already initially in READY state. When the
824 * subscription has no tables, we leave the twophase state as
825 * PENDING, to allow ALTER SUBSCRIPTION ... REFRESH
826 * PUBLICATION to work.
827 */
828 if (opts.twophase && !opts.copy_data && tables != NIL)
829 twophase_enabled = true;
830
831 walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled,
832 opts.failover, CRS_NOEXPORT_SNAPSHOT, NULL);
833
834 if (twophase_enabled)
835 UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED);
836
838 (errmsg("created replication slot \"%s\" on publisher",
839 opts.slot_name)));
840 }
841 }
842 PG_FINALLY();
843 {
845 }
846 PG_END_TRY();
847 }
848 else
850 (errmsg("subscription was created, but is not connected"),
851 errhint("To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.")));
852
854
856
857 /*
858 * Notify the launcher to start the apply worker if the subscription is
859 * enabled, or to create the conflict detection slot if retain_dead_tuples
860 * is enabled.
861 *
862 * Creating the conflict detection slot is essential even when the
863 * subscription is not enabled. This ensures that dead tuples are
864 * retained, which is necessary for accurately identifying the type of
865 * conflict during replication.
866 */
867 if (opts.enabled || opts.retaindeadtuples)
869
870 ObjectAddressSet(myself, SubscriptionRelationId, subid);
871
872 InvokeObjectPostCreateHook(SubscriptionRelationId, subid, 0);
873
874 return myself;
875}
bool has_privs_of_role(Oid member, Oid role)
Definition: acl.c:5284
#define OidIsValid(objectId)
Definition: c.h:774
Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
Definition: catalog.c:448
int errdetail(const char *fmt,...)
Definition: elog.c:1207
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
Definition: heaptuple.c:1117
void CatalogTupleInsert(Relation heapRel, HeapTuple tup)
Definition: indexing.c:233
#define InvokeObjectPostCreateHook(classId, objectId, subId)
Definition: objectaccess.h:173
RepOriginId replorigin_create(const char *roname)
Definition: origin.c:257
void recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
Definition: pg_shdepend.c:168
void pgstat_create_subscription(Oid subid)
#define SUBOPT_CREATE_SLOT
#define SUBOPT_CONNECT
bool superuser_arg(Oid roleid)
Definition: superuser.c:56
#define GetSysCacheOid2(cacheId, oidcol, key1, key2)
Definition: syscache.h:111
void UpdateTwoPhaseState(Oid suboid, char new_state)
Definition: tablesync.c:1821
#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
Definition: walreceiver.h:459
@ CRS_NOEXPORT_SNAPSHOT
Definition: walsender.h:23

References AccessShareLock, ACL_CREATE, aclcheck_error(), ACLCHECK_OK, AddSubscriptionRelState(), ApplyLauncherWakeupAtCommit(), Assert(), BoolGetDatum(), CatalogTupleInsert(), CharGetDatum(), check_pub_dead_tuple_retention(), check_publications(), check_publications_origin(), CheckSubDeadTupleRetention(), CheckSubscriptionRelkind(), CRS_NOEXPORT_SNAPSHOT, CStringGetDatum(), CStringGetTextDatum, DirectFunctionCall1, elog, ereport, err(), errcode(), ERRCODE_DUPLICATE_OBJECT, errdetail(), errhint(), errmsg(), ERROR, fetch_table_list(), get_database_name(), get_rel_relkind(), GetNewOidWithIndex(), GetSysCacheOid2, GetUserId(), has_privs_of_role(), heap_form_tuple(), heap_freetuple(), Int32GetDatum(), InvalidOid, InvalidXLogRecPtr, InvokeObjectPostCreateHook, IsSet, lfirst, load_file(), LSNGetDatum(), MyDatabaseId, NAMEDATALEN, namein(), NIL, NOTICE, object_aclcheck(), OBJECT_DATABASE, ObjectAddressSet, ObjectIdGetDatum(), OidIsValid, opts, parse_subscription_options(), PG_END_TRY, PG_FINALLY, PG_TRY, pgstat_create_subscription(), PreventInTransactionBlock(), publicationListToArray(), RangeVarGetRelid, recordDependencyOnOwner(), RelationGetDescr, RangeVar::relname, ReplicationOriginNameForLogicalRep(), replorigin_create(), RowExclusiveLock, RangeVar::schemaname, stmt, SUBOPT_BINARY, SUBOPT_CONNECT, SUBOPT_COPY_DATA, SUBOPT_CREATE_SLOT, SUBOPT_DISABLE_ON_ERR, SUBOPT_ENABLED, SUBOPT_FAILOVER, SUBOPT_MAX_RETENTION_DURATION, SUBOPT_ORIGIN, SUBOPT_PASSWORD_REQUIRED, SUBOPT_RETAIN_DEAD_TUPLES, SUBOPT_RUN_AS_OWNER, SUBOPT_SLOT_NAME, SUBOPT_STREAMING, SUBOPT_SYNCHRONOUS_COMMIT, SUBOPT_TWOPHASE_COMMIT, superuser(), superuser_arg(), table_close(), table_open(), UpdateTwoPhaseState(), values, walrcv_check_conninfo, walrcv_connect, walrcv_create_slot, walrcv_disconnect, WARNING, and wrconn.

Referenced by ProcessUtilitySlow().

◆ defGetStreamingMode()

char defGetStreamingMode ( DefElem def)

Definition at line 2855 of file subscriptioncmds.c.

2856{
2857 /*
2858 * If no parameter value given, assume "true" is meant.
2859 */
2860 if (!def->arg)
2861 return LOGICALREP_STREAM_ON;
2862
2863 /*
2864 * Allow 0, 1, "false", "true", "off", "on" or "parallel".
2865 */
2866 switch (nodeTag(def->arg))
2867 {
2868 case T_Integer:
2869 switch (intVal(def->arg))
2870 {
2871 case 0:
2872 return LOGICALREP_STREAM_OFF;
2873 case 1:
2874 return LOGICALREP_STREAM_ON;
2875 default:
2876 /* otherwise, error out below */
2877 break;
2878 }
2879 break;
2880 default:
2881 {
2882 char *sval = defGetString(def);
2883
2884 /*
2885 * The set of strings accepted here should match up with the
2886 * grammar's opt_boolean_or_string production.
2887 */
2888 if (pg_strcasecmp(sval, "false") == 0 ||
2889 pg_strcasecmp(sval, "off") == 0)
2890 return LOGICALREP_STREAM_OFF;
2891 if (pg_strcasecmp(sval, "true") == 0 ||
2892 pg_strcasecmp(sval, "on") == 0)
2893 return LOGICALREP_STREAM_ON;
2894 if (pg_strcasecmp(sval, "parallel") == 0)
2895 return LOGICALREP_STREAM_PARALLEL;
2896 }
2897 break;
2898 }
2899
2900 ereport(ERROR,
2901 (errcode(ERRCODE_SYNTAX_ERROR),
2902 errmsg("%s requires a Boolean value or \"parallel\"",
2903 def->defname)));
2904 return LOGICALREP_STREAM_OFF; /* keep compiler quiet */
2905}
char * defGetString(DefElem *def)
Definition: define.c:35
#define nodeTag(nodeptr)
Definition: nodes.h:139
char * defname
Definition: parsenodes.h:842
Node * arg
Definition: parsenodes.h:843
#define intVal(v)
Definition: value.h:79

References DefElem::arg, defGetString(), DefElem::defname, ereport, errcode(), errmsg(), ERROR, intVal, nodeTag, and pg_strcasecmp().

Referenced by parse_output_parameters(), and parse_subscription_options().

◆ DropSubscription()

void DropSubscription ( DropSubscriptionStmt stmt,
bool  isTopLevel 
)

Definition at line 1858 of file subscriptioncmds.c.

1859{
1860 Relation rel;
1861 ObjectAddress myself;
1862 HeapTuple tup;
1863 Oid subid;
1864 Oid subowner;
1865 Datum datum;
1866 bool isnull;
1867 char *subname;
1868 char *conninfo;
1869 char *slotname;
1870 List *subworkers;
1871 ListCell *lc;
1872 char originname[NAMEDATALEN];
1873 char *err = NULL;
1876 List *rstates;
1877 bool must_use_password;
1878
1879 /*
1880 * The launcher may concurrently start a new worker for this subscription.
1881 * During initialization, the worker checks for subscription validity and
1882 * exits if the subscription has already been dropped. See
1883 * InitializeLogRepWorker.
1884 */
1885 rel = table_open(SubscriptionRelationId, RowExclusiveLock);
1886
1887 tup = SearchSysCache2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
1888 CStringGetDatum(stmt->subname));
1889
1890 if (!HeapTupleIsValid(tup))
1891 {
1892 table_close(rel, NoLock);
1893
1894 if (!stmt->missing_ok)
1895 ereport(ERROR,
1896 (errcode(ERRCODE_UNDEFINED_OBJECT),
1897 errmsg("subscription \"%s\" does not exist",
1898 stmt->subname)));
1899 else
1901 (errmsg("subscription \"%s\" does not exist, skipping",
1902 stmt->subname)));
1903
1904 return;
1905 }
1906
1907 form = (Form_pg_subscription) GETSTRUCT(tup);
1908 subid = form->oid;
1909 subowner = form->subowner;
1910 must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
1911
1912 /* must be owner */
1913 if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
1915 stmt->subname);
1916
1917 /* DROP hook for the subscription being removed */
1918 InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
1919
1920 /*
1921 * Lock the subscription so nobody else can do anything with it (including
1922 * the replication workers).
1923 */
1924 LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
1925
1926 /* Get subname */
1927 datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
1928 Anum_pg_subscription_subname);
1929 subname = pstrdup(NameStr(*DatumGetName(datum)));
1930
1931 /* Get conninfo */
1932 datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
1933 Anum_pg_subscription_subconninfo);
1934 conninfo = TextDatumGetCString(datum);
1935
1936 /* Get slotname */
1937 datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
1938 Anum_pg_subscription_subslotname, &isnull);
1939 if (!isnull)
1940 slotname = pstrdup(NameStr(*DatumGetName(datum)));
1941 else
1942 slotname = NULL;
1943
1944 /*
1945 * Since dropping a replication slot is not transactional, the replication
1946 * slot stays dropped even if the transaction rolls back. So we cannot
1947 * run DROP SUBSCRIPTION inside a transaction block if dropping the
1948 * replication slot. Also, in this case, we report a message for dropping
1949 * the subscription to the cumulative stats system.
1950 *
1951 * XXX The command name should really be something like "DROP SUBSCRIPTION
1952 * of a subscription that is associated with a replication slot", but we
1953 * don't have the proper facilities for that.
1954 */
1955 if (slotname)
1956 PreventInTransactionBlock(isTopLevel, "DROP SUBSCRIPTION");
1957
1958 ObjectAddressSet(myself, SubscriptionRelationId, subid);
1959 EventTriggerSQLDropAddObject(&myself, true, true);
1960
1961 /* Remove the tuple from catalog. */
1962 CatalogTupleDelete(rel, &tup->t_self);
1963
1964 ReleaseSysCache(tup);
1965
1966 /*
1967 * Stop all the subscription workers immediately.
1968 *
1969 * This is necessary if we are dropping the replication slot, so that the
1970 * slot becomes accessible.
1971 *
1972 * It is also necessary if the subscription is disabled and was disabled
1973 * in the same transaction. Then the workers haven't seen the disabling
1974 * yet and will still be running, leading to hangs later when we want to
1975 * drop the replication origin. If the subscription was disabled before
1976 * this transaction, then there shouldn't be any workers left, so this
1977 * won't make a difference.
1978 *
1979 * New workers won't be started because we hold an exclusive lock on the
1980 * subscription till the end of the transaction.
1981 */
1982 subworkers = logicalrep_workers_find(subid, false, true);
1983 foreach(lc, subworkers)
1984 {
1986
1988 }
1989 list_free(subworkers);
1990
1991 /*
1992 * Remove the no-longer-useful entry in the launcher's table of apply
1993 * worker start times.
1994 *
1995 * If this transaction rolls back, the launcher might restart a failed
1996 * apply worker before wal_retrieve_retry_interval milliseconds have
1997 * elapsed, but that's pretty harmless.
1998 */
2000
2001 /*
2002 * Cleanup of tablesync replication origins.
2003 *
2004 * Any READY-state relations would already have dealt with clean-ups.
2005 *
2006 * Note that the state can't change because we have already stopped both
2007 * the apply and tablesync workers and they can't restart because of
2008 * exclusive lock on the subscription.
2009 */
2010 rstates = GetSubscriptionRelations(subid, true);
2011 foreach(lc, rstates)
2012 {
2014 Oid relid = rstate->relid;
2015
2016 /* Only cleanup resources of tablesync workers */
2017 if (!OidIsValid(relid))
2018 continue;
2019
2020 /*
2021 * Drop the tablesync's origin tracking if exists.
2022 *
2023 * It is possible that the origin is not yet created for tablesync
2024 * worker so passing missing_ok = true. This can happen for the states
2025 * before SUBREL_STATE_FINISHEDCOPY.
2026 */
2027 ReplicationOriginNameForLogicalRep(subid, relid, originname,
2028 sizeof(originname));
2029 replorigin_drop_by_name(originname, true, false);
2030 }
2031
2032 /* Clean up dependencies */
2033 deleteSharedDependencyRecordsFor(SubscriptionRelationId, subid, 0);
2034
2035 /* Remove any associated relation synchronization states. */
2037
2038 /* Remove the origin tracking if exists. */
2039 ReplicationOriginNameForLogicalRep(subid, InvalidOid, originname, sizeof(originname));
2040 replorigin_drop_by_name(originname, true, false);
2041
2042 /*
2043 * Tell the cumulative stats system that the subscription is getting
2044 * dropped.
2045 */
2047
2048 /*
2049 * If there is no slot associated with the subscription, we can finish
2050 * here.
2051 */
2052 if (!slotname && rstates == NIL)
2053 {
2054 table_close(rel, NoLock);
2055 return;
2056 }
2057
2058 /*
2059 * Try to acquire the connection necessary for dropping slots.
2060 *
2061 * Note: If the slotname is NONE/NULL then we allow the command to finish
2062 * and users need to manually cleanup the apply and tablesync worker slots
2063 * later.
2064 *
2065 * This has to be at the end because otherwise if there is an error while
2066 * doing the database operations we won't be able to rollback dropped
2067 * slot.
2068 */
2069 load_file("libpqwalreceiver", false);
2070
2071 wrconn = walrcv_connect(conninfo, true, true, must_use_password,
2072 subname, &err);
2073 if (wrconn == NULL)
2074 {
2075 if (!slotname)
2076 {
2077 /* be tidy */
2078 list_free(rstates);
2079 table_close(rel, NoLock);
2080 return;
2081 }
2082 else
2083 {
2084 ReportSlotConnectionError(rstates, subid, slotname, err);
2085 }
2086 }
2087
2088 PG_TRY();
2089 {
2090 foreach(lc, rstates)
2091 {
2093 Oid relid = rstate->relid;
2094
2095 /* Only cleanup resources of tablesync workers */
2096 if (!OidIsValid(relid))
2097 continue;
2098
2099 /*
2100 * Drop the tablesync slots associated with removed tables.
2101 *
2102 * For SYNCDONE/READY states, the tablesync slot is known to have
2103 * already been dropped by the tablesync worker.
2104 *
2105 * For other states, there is no certainty, maybe the slot does
2106 * not exist yet. Also, if we fail after removing some of the
2107 * slots, next time, it will again try to drop already dropped
2108 * slots and fail. For these reasons, we allow missing_ok = true
2109 * for the drop.
2110 */
2111 if (rstate->state != SUBREL_STATE_SYNCDONE)
2112 {
2113 char syncslotname[NAMEDATALEN] = {0};
2114
2115 ReplicationSlotNameForTablesync(subid, relid, syncslotname,
2116 sizeof(syncslotname));
2117 ReplicationSlotDropAtPubNode(wrconn, syncslotname, true);
2118 }
2119 }
2120
2121 list_free(rstates);
2122
2123 /*
2124 * If there is a slot associated with the subscription, then drop the
2125 * replication slot at the publisher.
2126 */
2127 if (slotname)
2128 ReplicationSlotDropAtPubNode(wrconn, slotname, false);
2129 }
2130 PG_FINALLY();
2131 {
2133 }
2134 PG_END_TRY();
2135
2136 table_close(rel, NoLock);
2137}
void EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal)
void CatalogTupleDelete(Relation heapRel, ItemPointer tid)
Definition: indexing.c:365
void ApplyLauncherForgetWorkerStartTime(Oid subid)
Definition: launcher.c:1101
void list_free(List *list)
Definition: list.c:1546
char * pstrdup(const char *in)
Definition: mcxt.c:1759
#define InvokeObjectDropHook(classId, objectId, subId)
Definition: objectaccess.h:182
void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
Definition: pg_shdepend.c:1047
void pgstat_drop_subscription(Oid subid)
static Name DatumGetName(Datum X)
Definition: postgres.h:370
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err)
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:264
Datum SysCacheGetAttr(int cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition: syscache.c:595
HeapTuple SearchSysCache2(int cacheId, Datum key1, Datum key2)
Definition: syscache.c:230
Datum SysCacheGetAttrNotNull(int cacheId, HeapTuple tup, AttrNumber attributeNumber)
Definition: syscache.c:625

References AccessExclusiveLock, aclcheck_error(), ACLCHECK_NOT_OWNER, ApplyLauncherForgetWorkerStartTime(), CatalogTupleDelete(), CStringGetDatum(), DatumGetName(), deleteSharedDependencyRecordsFor(), ereport, err(), errcode(), errmsg(), ERROR, EventTriggerSQLDropAddObject(), GETSTRUCT(), GetSubscriptionRelations(), GetUserId(), HeapTupleIsValid, InvalidOid, InvokeObjectDropHook, lfirst, list_free(), load_file(), LockSharedObject(), logicalrep_worker_stop(), logicalrep_workers_find(), MyDatabaseId, NAMEDATALEN, NameStr, NIL, NoLock, NOTICE, object_ownercheck(), OBJECT_SUBSCRIPTION, ObjectAddressSet, ObjectIdGetDatum(), OidIsValid, PG_END_TRY, PG_FINALLY, PG_TRY, pgstat_drop_subscription(), PreventInTransactionBlock(), pstrdup(), ReleaseSysCache(), SubscriptionRelState::relid, LogicalRepWorker::relid, RemoveSubscriptionRel(), ReplicationOriginNameForLogicalRep(), ReplicationSlotDropAtPubNode(), ReplicationSlotNameForTablesync(), replorigin_drop_by_name(), ReportSlotConnectionError(), RowExclusiveLock, SearchSysCache2(), SubscriptionRelState::state, stmt, LogicalRepWorker::subid, subname, superuser_arg(), SysCacheGetAttr(), SysCacheGetAttrNotNull(), HeapTupleData::t_self, table_close(), table_open(), TextDatumGetCString, walrcv_connect, walrcv_disconnect, and wrconn.

Referenced by ProcessUtilitySlow().

◆ fetch_table_list()

static List * fetch_table_list ( WalReceiverConn wrconn,
List publications 
)
static

Definition at line 2605 of file subscriptioncmds.c.

2606{
2607 WalRcvExecResult *res;
2608 StringInfoData cmd;
2609 TupleTableSlot *slot;
2610 Oid tableRow[3] = {TEXTOID, TEXTOID, InvalidOid};
2611 List *tablelist = NIL;
2613 bool check_columnlist = (server_version >= 150000);
2614 StringInfo pub_names = makeStringInfo();
2615
2616 initStringInfo(&cmd);
2617
2618 /* Build the pub_names comma-separated string. */
2619 GetPublicationsStr(publications, pub_names, true);
2620
2621 /* Get the list of tables from the publisher. */
2622 if (server_version >= 160000)
2623 {
2624 tableRow[2] = INT2VECTOROID;
2625
2626 /*
2627 * From version 16, we allowed passing multiple publications to the
2628 * function pg_get_publication_tables. This helped to filter out the
2629 * partition table whose ancestor is also published in this
2630 * publication array.
2631 *
2632 * Join pg_get_publication_tables with pg_publication to exclude
2633 * non-existing publications.
2634 *
2635 * Note that attrs are always stored in sorted order so we don't need
2636 * to worry if different publications have specified them in a
2637 * different order. See pub_collist_validate.
2638 */
2639 appendStringInfo(&cmd, "SELECT DISTINCT n.nspname, c.relname, gpt.attrs\n"
2640 " FROM pg_class c\n"
2641 " JOIN pg_namespace n ON n.oid = c.relnamespace\n"
2642 " JOIN ( SELECT (pg_get_publication_tables(VARIADIC array_agg(pubname::text))).*\n"
2643 " FROM pg_publication\n"
2644 " WHERE pubname IN ( %s )) AS gpt\n"
2645 " ON gpt.relid = c.oid\n",
2646 pub_names->data);
2647 }
2648 else
2649 {
2650 tableRow[2] = NAMEARRAYOID;
2651 appendStringInfoString(&cmd, "SELECT DISTINCT t.schemaname, t.tablename \n");
2652
2653 /* Get column lists for each relation if the publisher supports it */
2654 if (check_columnlist)
2655 appendStringInfoString(&cmd, ", t.attnames\n");
2656
2657 appendStringInfo(&cmd, "FROM pg_catalog.pg_publication_tables t\n"
2658 " WHERE t.pubname IN ( %s )",
2659 pub_names->data);
2660 }
2661
2662 destroyStringInfo(pub_names);
2663
2664 res = walrcv_exec(wrconn, cmd.data, check_columnlist ? 3 : 2, tableRow);
2665 pfree(cmd.data);
2666
2667 if (res->status != WALRCV_OK_TUPLES)
2668 ereport(ERROR,
2669 (errcode(ERRCODE_CONNECTION_FAILURE),
2670 errmsg("could not receive list of replicated tables from the publisher: %s",
2671 res->err)));
2672
2673 /* Process tables. */
2675 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
2676 {
2677 char *nspname;
2678 char *relname;
2679 bool isnull;
2680 RangeVar *rv;
2681
2682 nspname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
2683 Assert(!isnull);
2684 relname = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
2685 Assert(!isnull);
2686
2687 rv = makeRangeVar(nspname, relname, -1);
2688
2689 if (check_columnlist && list_member(tablelist, rv))
2690 ereport(ERROR,
2691 errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2692 errmsg("cannot use different column lists for table \"%s.%s\" in different publications",
2693 nspname, relname));
2694 else
2695 tablelist = lappend(tablelist, rv);
2696
2697 ExecClearTuple(slot);
2698 }
2700
2702
2703 return tablelist;
2704}
List * lappend(List *list, void *datum)
Definition: list.c:339
bool list_member(const List *list, const void *datum)
Definition: list.c:661
RangeVar * makeRangeVar(char *schemaname, char *relname, int location)
Definition: makefuncs.c:473
NameData relname
Definition: pg_class.h:38
static int server_version
Definition: pg_dumpall.c:109

References appendStringInfo(), appendStringInfoString(), Assert(), StringInfoData::data, destroyStringInfo(), ereport, WalRcvExecResult::err, errcode(), errmsg(), ERROR, ExecClearTuple(), ExecDropSingleTupleTableSlot(), GetPublicationsStr(), initStringInfo(), InvalidOid, lappend(), list_member(), makeRangeVar(), MakeSingleTupleTableSlot(), makeStringInfo(), NIL, pfree(), relname, server_version, slot_getattr(), WalRcvExecResult::status, TextDatumGetCString, TTSOpsMinimalTuple, WalRcvExecResult::tupledesc, WalRcvExecResult::tuplestore, tuplestore_gettupleslot(), walrcv_clear_result(), walrcv_exec, WALRCV_OK_TUPLES, walrcv_server_version, and wrconn.

Referenced by AlterSubscription_refresh(), and CreateSubscription().

◆ merge_publications()

static List * merge_publications ( List oldpublist,
List newpublist,
bool  addpub,
const char *  subname 
)
static

Definition at line 2796 of file subscriptioncmds.c.

2797{
2798 ListCell *lc;
2799
2800 oldpublist = list_copy(oldpublist);
2801
2802 check_duplicates_in_publist(newpublist, NULL);
2803
2804 foreach(lc, newpublist)
2805 {
2806 char *name = strVal(lfirst(lc));
2807 ListCell *lc2;
2808 bool found = false;
2809
2810 foreach(lc2, oldpublist)
2811 {
2812 char *pubname = strVal(lfirst(lc2));
2813
2814 if (strcmp(name, pubname) == 0)
2815 {
2816 found = true;
2817 if (addpub)
2818 ereport(ERROR,
2820 errmsg("publication \"%s\" is already in subscription \"%s\"",
2821 name, subname)));
2822 else
2823 oldpublist = foreach_delete_current(oldpublist, lc2);
2824
2825 break;
2826 }
2827 }
2828
2829 if (addpub && !found)
2830 oldpublist = lappend(oldpublist, makeString(name));
2831 else if (!addpub && !found)
2832 ereport(ERROR,
2833 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2834 errmsg("publication \"%s\" is not in subscription \"%s\"",
2835 name, subname)));
2836 }
2837
2838 /*
2839 * XXX Probably no strong reason for this, but for now it's to make ALTER
2840 * SUBSCRIPTION ... DROP PUBLICATION consistent with SET PUBLICATION.
2841 */
2842 if (!oldpublist)
2843 ereport(ERROR,
2844 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
2845 errmsg("cannot drop all the publications from a subscription")));
2846
2847 return oldpublist;
2848}
#define foreach_delete_current(lst, var_or_cell)
Definition: pg_list.h:391
static void check_duplicates_in_publist(List *publist, Datum *datums)

References check_duplicates_in_publist(), ereport, errcode(), ERRCODE_DUPLICATE_OBJECT, errmsg(), ERROR, foreach_delete_current, lappend(), lfirst, list_copy(), makeString(), name, strVal, and subname.

Referenced by AlterSubscription().

◆ parse_subscription_options()

static void parse_subscription_options ( ParseState pstate,
List stmt_options,
bits32  supported_opts,
SubOpts opts 
)
static

Definition at line 130 of file subscriptioncmds.c.

132{
133 ListCell *lc;
134
135 /* Start out with cleared opts. */
136 memset(opts, 0, sizeof(SubOpts));
137
138 /* caller must expect some option */
139 Assert(supported_opts != 0);
140
141 /* If connect option is supported, these others also need to be. */
142 Assert(!IsSet(supported_opts, SUBOPT_CONNECT) ||
143 IsSet(supported_opts, SUBOPT_ENABLED | SUBOPT_CREATE_SLOT |
145
146 /* Set default values for the supported options. */
147 if (IsSet(supported_opts, SUBOPT_CONNECT))
148 opts->connect = true;
149 if (IsSet(supported_opts, SUBOPT_ENABLED))
150 opts->enabled = true;
151 if (IsSet(supported_opts, SUBOPT_CREATE_SLOT))
152 opts->create_slot = true;
153 if (IsSet(supported_opts, SUBOPT_COPY_DATA))
154 opts->copy_data = true;
155 if (IsSet(supported_opts, SUBOPT_REFRESH))
156 opts->refresh = true;
157 if (IsSet(supported_opts, SUBOPT_BINARY))
158 opts->binary = false;
159 if (IsSet(supported_opts, SUBOPT_STREAMING))
160 opts->streaming = LOGICALREP_STREAM_PARALLEL;
161 if (IsSet(supported_opts, SUBOPT_TWOPHASE_COMMIT))
162 opts->twophase = false;
163 if (IsSet(supported_opts, SUBOPT_DISABLE_ON_ERR))
164 opts->disableonerr = false;
165 if (IsSet(supported_opts, SUBOPT_PASSWORD_REQUIRED))
166 opts->passwordrequired = true;
167 if (IsSet(supported_opts, SUBOPT_RUN_AS_OWNER))
168 opts->runasowner = false;
169 if (IsSet(supported_opts, SUBOPT_FAILOVER))
170 opts->failover = false;
171 if (IsSet(supported_opts, SUBOPT_RETAIN_DEAD_TUPLES))
172 opts->retaindeadtuples = false;
173 if (IsSet(supported_opts, SUBOPT_MAX_RETENTION_DURATION))
174 opts->maxretention = 0;
175 if (IsSet(supported_opts, SUBOPT_ORIGIN))
176 opts->origin = pstrdup(LOGICALREP_ORIGIN_ANY);
177
178 /* Parse options */
179 foreach(lc, stmt_options)
180 {
181 DefElem *defel = (DefElem *) lfirst(lc);
182
183 if (IsSet(supported_opts, SUBOPT_CONNECT) &&
184 strcmp(defel->defname, "connect") == 0)
185 {
186 if (IsSet(opts->specified_opts, SUBOPT_CONNECT))
187 errorConflictingDefElem(defel, pstate);
188
189 opts->specified_opts |= SUBOPT_CONNECT;
190 opts->connect = defGetBoolean(defel);
191 }
192 else if (IsSet(supported_opts, SUBOPT_ENABLED) &&
193 strcmp(defel->defname, "enabled") == 0)
194 {
195 if (IsSet(opts->specified_opts, SUBOPT_ENABLED))
196 errorConflictingDefElem(defel, pstate);
197
198 opts->specified_opts |= SUBOPT_ENABLED;
199 opts->enabled = defGetBoolean(defel);
200 }
201 else if (IsSet(supported_opts, SUBOPT_CREATE_SLOT) &&
202 strcmp(defel->defname, "create_slot") == 0)
203 {
204 if (IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
205 errorConflictingDefElem(defel, pstate);
206
207 opts->specified_opts |= SUBOPT_CREATE_SLOT;
208 opts->create_slot = defGetBoolean(defel);
209 }
210 else if (IsSet(supported_opts, SUBOPT_SLOT_NAME) &&
211 strcmp(defel->defname, "slot_name") == 0)
212 {
213 if (IsSet(opts->specified_opts, SUBOPT_SLOT_NAME))
214 errorConflictingDefElem(defel, pstate);
215
216 opts->specified_opts |= SUBOPT_SLOT_NAME;
217 opts->slot_name = defGetString(defel);
218
219 /* Setting slot_name = NONE is treated as no slot name. */
220 if (strcmp(opts->slot_name, "none") == 0)
221 opts->slot_name = NULL;
222 else
223 ReplicationSlotValidateName(opts->slot_name, false, ERROR);
224 }
225 else if (IsSet(supported_opts, SUBOPT_COPY_DATA) &&
226 strcmp(defel->defname, "copy_data") == 0)
227 {
228 if (IsSet(opts->specified_opts, SUBOPT_COPY_DATA))
229 errorConflictingDefElem(defel, pstate);
230
231 opts->specified_opts |= SUBOPT_COPY_DATA;
232 opts->copy_data = defGetBoolean(defel);
233 }
234 else if (IsSet(supported_opts, SUBOPT_SYNCHRONOUS_COMMIT) &&
235 strcmp(defel->defname, "synchronous_commit") == 0)
236 {
237 if (IsSet(opts->specified_opts, SUBOPT_SYNCHRONOUS_COMMIT))
238 errorConflictingDefElem(defel, pstate);
239
240 opts->specified_opts |= SUBOPT_SYNCHRONOUS_COMMIT;
241 opts->synchronous_commit = defGetString(defel);
242
243 /* Test if the given value is valid for synchronous_commit GUC. */
244 (void) set_config_option("synchronous_commit", opts->synchronous_commit,
246 false, 0, false);
247 }
248 else if (IsSet(supported_opts, SUBOPT_REFRESH) &&
249 strcmp(defel->defname, "refresh") == 0)
250 {
251 if (IsSet(opts->specified_opts, SUBOPT_REFRESH))
252 errorConflictingDefElem(defel, pstate);
253
254 opts->specified_opts |= SUBOPT_REFRESH;
255 opts->refresh = defGetBoolean(defel);
256 }
257 else if (IsSet(supported_opts, SUBOPT_BINARY) &&
258 strcmp(defel->defname, "binary") == 0)
259 {
260 if (IsSet(opts->specified_opts, SUBOPT_BINARY))
261 errorConflictingDefElem(defel, pstate);
262
263 opts->specified_opts |= SUBOPT_BINARY;
264 opts->binary = defGetBoolean(defel);
265 }
266 else if (IsSet(supported_opts, SUBOPT_STREAMING) &&
267 strcmp(defel->defname, "streaming") == 0)
268 {
269 if (IsSet(opts->specified_opts, SUBOPT_STREAMING))
270 errorConflictingDefElem(defel, pstate);
271
272 opts->specified_opts |= SUBOPT_STREAMING;
273 opts->streaming = defGetStreamingMode(defel);
274 }
275 else if (IsSet(supported_opts, SUBOPT_TWOPHASE_COMMIT) &&
276 strcmp(defel->defname, "two_phase") == 0)
277 {
278 if (IsSet(opts->specified_opts, SUBOPT_TWOPHASE_COMMIT))
279 errorConflictingDefElem(defel, pstate);
280
281 opts->specified_opts |= SUBOPT_TWOPHASE_COMMIT;
282 opts->twophase = defGetBoolean(defel);
283 }
284 else if (IsSet(supported_opts, SUBOPT_DISABLE_ON_ERR) &&
285 strcmp(defel->defname, "disable_on_error") == 0)
286 {
287 if (IsSet(opts->specified_opts, SUBOPT_DISABLE_ON_ERR))
288 errorConflictingDefElem(defel, pstate);
289
290 opts->specified_opts |= SUBOPT_DISABLE_ON_ERR;
291 opts->disableonerr = defGetBoolean(defel);
292 }
293 else if (IsSet(supported_opts, SUBOPT_PASSWORD_REQUIRED) &&
294 strcmp(defel->defname, "password_required") == 0)
295 {
296 if (IsSet(opts->specified_opts, SUBOPT_PASSWORD_REQUIRED))
297 errorConflictingDefElem(defel, pstate);
298
299 opts->specified_opts |= SUBOPT_PASSWORD_REQUIRED;
300 opts->passwordrequired = defGetBoolean(defel);
301 }
302 else if (IsSet(supported_opts, SUBOPT_RUN_AS_OWNER) &&
303 strcmp(defel->defname, "run_as_owner") == 0)
304 {
305 if (IsSet(opts->specified_opts, SUBOPT_RUN_AS_OWNER))
306 errorConflictingDefElem(defel, pstate);
307
308 opts->specified_opts |= SUBOPT_RUN_AS_OWNER;
309 opts->runasowner = defGetBoolean(defel);
310 }
311 else if (IsSet(supported_opts, SUBOPT_FAILOVER) &&
312 strcmp(defel->defname, "failover") == 0)
313 {
314 if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
315 errorConflictingDefElem(defel, pstate);
316
317 opts->specified_opts |= SUBOPT_FAILOVER;
318 opts->failover = defGetBoolean(defel);
319 }
320 else if (IsSet(supported_opts, SUBOPT_RETAIN_DEAD_TUPLES) &&
321 strcmp(defel->defname, "retain_dead_tuples") == 0)
322 {
323 if (IsSet(opts->specified_opts, SUBOPT_RETAIN_DEAD_TUPLES))
324 errorConflictingDefElem(defel, pstate);
325
326 opts->specified_opts |= SUBOPT_RETAIN_DEAD_TUPLES;
327 opts->retaindeadtuples = defGetBoolean(defel);
328 }
329 else if (IsSet(supported_opts, SUBOPT_MAX_RETENTION_DURATION) &&
330 strcmp(defel->defname, "max_retention_duration") == 0)
331 {
332 if (IsSet(opts->specified_opts, SUBOPT_MAX_RETENTION_DURATION))
333 errorConflictingDefElem(defel, pstate);
334
335 opts->specified_opts |= SUBOPT_MAX_RETENTION_DURATION;
336 opts->maxretention = defGetInt32(defel);
337 }
338 else if (IsSet(supported_opts, SUBOPT_ORIGIN) &&
339 strcmp(defel->defname, "origin") == 0)
340 {
341 if (IsSet(opts->specified_opts, SUBOPT_ORIGIN))
342 errorConflictingDefElem(defel, pstate);
343
344 opts->specified_opts |= SUBOPT_ORIGIN;
345 pfree(opts->origin);
346
347 /*
348 * Even though the "origin" parameter allows only "none" and "any"
349 * values, it is implemented as a string type so that the
350 * parameter can be extended in future versions to support
351 * filtering using origin names specified by the user.
352 */
353 opts->origin = defGetString(defel);
354
355 if ((pg_strcasecmp(opts->origin, LOGICALREP_ORIGIN_NONE) != 0) &&
356 (pg_strcasecmp(opts->origin, LOGICALREP_ORIGIN_ANY) != 0))
358 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
359 errmsg("unrecognized origin value: \"%s\"", opts->origin));
360 }
361 else if (IsSet(supported_opts, SUBOPT_LSN) &&
362 strcmp(defel->defname, "lsn") == 0)
363 {
364 char *lsn_str = defGetString(defel);
365 XLogRecPtr lsn;
366
367 if (IsSet(opts->specified_opts, SUBOPT_LSN))
368 errorConflictingDefElem(defel, pstate);
369
370 /* Setting lsn = NONE is treated as resetting LSN */
371 if (strcmp(lsn_str, "none") == 0)
372 lsn = InvalidXLogRecPtr;
373 else
374 {
375 /* Parse the argument as LSN */
377 CStringGetDatum(lsn_str)));
378
379 if (XLogRecPtrIsInvalid(lsn))
381 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
382 errmsg("invalid WAL location (LSN): %s", lsn_str)));
383 }
384
385 opts->specified_opts |= SUBOPT_LSN;
386 opts->lsn = lsn;
387 }
388 else
390 (errcode(ERRCODE_SYNTAX_ERROR),
391 errmsg("unrecognized subscription parameter: \"%s\"", defel->defname)));
392 }
393
394 /*
395 * We've been explicitly asked to not connect, that requires some
396 * additional processing.
397 */
398 if (!opts->connect && IsSet(supported_opts, SUBOPT_CONNECT))
399 {
400 /* Check for incompatible options from the user. */
401 if (opts->enabled &&
402 IsSet(opts->specified_opts, SUBOPT_ENABLED))
404 (errcode(ERRCODE_SYNTAX_ERROR),
405 /*- translator: both %s are strings of the form "option = value" */
406 errmsg("%s and %s are mutually exclusive options",
407 "connect = false", "enabled = true")));
408
409 if (opts->create_slot &&
410 IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
412 (errcode(ERRCODE_SYNTAX_ERROR),
413 errmsg("%s and %s are mutually exclusive options",
414 "connect = false", "create_slot = true")));
415
416 if (opts->copy_data &&
417 IsSet(opts->specified_opts, SUBOPT_COPY_DATA))
419 (errcode(ERRCODE_SYNTAX_ERROR),
420 errmsg("%s and %s are mutually exclusive options",
421 "connect = false", "copy_data = true")));
422
423 /* Change the defaults of other options. */
424 opts->enabled = false;
425 opts->create_slot = false;
426 opts->copy_data = false;
427 }
428
429 /*
430 * Do additional checking for disallowed combination when slot_name = NONE
431 * was used.
432 */
433 if (!opts->slot_name &&
434 IsSet(opts->specified_opts, SUBOPT_SLOT_NAME))
435 {
436 if (opts->enabled)
437 {
438 if (IsSet(opts->specified_opts, SUBOPT_ENABLED))
440 (errcode(ERRCODE_SYNTAX_ERROR),
441 /*- translator: both %s are strings of the form "option = value" */
442 errmsg("%s and %s are mutually exclusive options",
443 "slot_name = NONE", "enabled = true")));
444 else
446 (errcode(ERRCODE_SYNTAX_ERROR),
447 /*- translator: both %s are strings of the form "option = value" */
448 errmsg("subscription with %s must also set %s",
449 "slot_name = NONE", "enabled = false")));
450 }
451
452 if (opts->create_slot)
453 {
454 if (IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
456 (errcode(ERRCODE_SYNTAX_ERROR),
457 /*- translator: both %s are strings of the form "option = value" */
458 errmsg("%s and %s are mutually exclusive options",
459 "slot_name = NONE", "create_slot = true")));
460 else
462 (errcode(ERRCODE_SYNTAX_ERROR),
463 /*- translator: both %s are strings of the form "option = value" */
464 errmsg("subscription with %s must also set %s",
465 "slot_name = NONE", "create_slot = false")));
466 }
467 }
468}
int32 defGetInt32(DefElem *def)
Definition: define.c:149
bool defGetBoolean(DefElem *def)
Definition: define.c:94
void errorConflictingDefElem(DefElem *defel, ParseState *pstate)
Definition: define.c:371
int set_config_option(const char *name, const char *value, GucContext context, GucSource source, GucAction action, bool changeVal, int elevel, bool is_reload)
Definition: guc.c:3347
@ GUC_ACTION_SET
Definition: guc.h:203
@ PGC_S_TEST
Definition: guc.h:125
@ PGC_BACKEND
Definition: guc.h:77
Datum pg_lsn_in(PG_FUNCTION_ARGS)
Definition: pg_lsn.c:64
static XLogRecPtr DatumGetLSN(Datum X)
Definition: pg_lsn.h:25
bool ReplicationSlotValidateName(const char *name, bool allow_reserved_name, int elevel)
Definition: slot.c:272
char defGetStreamingMode(DefElem *def)

References Assert(), CStringGetDatum(), DatumGetLSN(), defGetBoolean(), defGetInt32(), defGetStreamingMode(), defGetString(), DefElem::defname, DirectFunctionCall1, ereport, errcode(), errmsg(), ERROR, errorConflictingDefElem(), GUC_ACTION_SET, InvalidXLogRecPtr, IsSet, lfirst, opts, pfree(), pg_lsn_in(), pg_strcasecmp(), PGC_BACKEND, PGC_S_TEST, pstrdup(), ReplicationSlotValidateName(), set_config_option(), SUBOPT_BINARY, SUBOPT_CONNECT, SUBOPT_COPY_DATA, SUBOPT_CREATE_SLOT, SUBOPT_DISABLE_ON_ERR, SUBOPT_ENABLED, SUBOPT_FAILOVER, SUBOPT_LSN, SUBOPT_MAX_RETENTION_DURATION, SUBOPT_ORIGIN, SUBOPT_PASSWORD_REQUIRED, SUBOPT_REFRESH, SUBOPT_RETAIN_DEAD_TUPLES, SUBOPT_RUN_AS_OWNER, SUBOPT_SLOT_NAME, SUBOPT_STREAMING, SUBOPT_SYNCHRONOUS_COMMIT, SUBOPT_TWOPHASE_COMMIT, and XLogRecPtrIsInvalid.

Referenced by AlterSubscription(), and CreateSubscription().

◆ publicationListToArray()

static Datum publicationListToArray ( List publist)
static

Definition at line 537 of file subscriptioncmds.c.

538{
539 ArrayType *arr;
540 Datum *datums;
541 MemoryContext memcxt;
542 MemoryContext oldcxt;
543
544 /* Create memory context for temporary allocations. */
546 "publicationListToArray to array",
548 oldcxt = MemoryContextSwitchTo(memcxt);
549
550 datums = (Datum *) palloc(sizeof(Datum) * list_length(publist));
551
552 check_duplicates_in_publist(publist, datums);
553
554 MemoryContextSwitchTo(oldcxt);
555
556 arr = construct_array_builtin(datums, list_length(publist), TEXTOID);
557
558 MemoryContextDelete(memcxt);
559
560 return PointerGetDatum(arr);
561}
ArrayType * construct_array_builtin(Datum *elems, int nelems, Oid elmtype)
Definition: arrayfuncs.c:3381
MemoryContext CurrentMemoryContext
Definition: mcxt.c:160
void MemoryContextDelete(MemoryContext context)
Definition: mcxt.c:469
#define AllocSetContextCreate
Definition: memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition: memutils.h:160
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
static Datum PointerGetDatum(const void *X)
Definition: postgres.h:332

References ALLOCSET_DEFAULT_SIZES, AllocSetContextCreate, check_duplicates_in_publist(), construct_array_builtin(), CurrentMemoryContext, list_length(), MemoryContextDelete(), MemoryContextSwitchTo(), palloc(), and PointerGetDatum().

Referenced by AlterSubscription(), and CreateSubscription().

◆ ReplicationSlotDropAtPubNode()

void ReplicationSlotDropAtPubNode ( WalReceiverConn wrconn,
char *  slotname,
bool  missing_ok 
)

Definition at line 2147 of file subscriptioncmds.c.

2148{
2149 StringInfoData cmd;
2150
2151 Assert(wrconn);
2152
2153 load_file("libpqwalreceiver", false);
2154
2155 initStringInfo(&cmd);
2156 appendStringInfo(&cmd, "DROP_REPLICATION_SLOT %s WAIT", quote_identifier(slotname));
2157
2158 PG_TRY();
2159 {
2160 WalRcvExecResult *res;
2161
2162 res = walrcv_exec(wrconn, cmd.data, 0, NULL);
2163
2164 if (res->status == WALRCV_OK_COMMAND)
2165 {
2166 /* NOTICE. Success. */
2168 (errmsg("dropped replication slot \"%s\" on publisher",
2169 slotname)));
2170 }
2171 else if (res->status == WALRCV_ERROR &&
2172 missing_ok &&
2173 res->sqlstate == ERRCODE_UNDEFINED_OBJECT)
2174 {
2175 /* LOG. Error, but missing_ok = true. */
2176 ereport(LOG,
2177 (errmsg("could not drop replication slot \"%s\" on publisher: %s",
2178 slotname, res->err)));
2179 }
2180 else
2181 {
2182 /* ERROR. */
2183 ereport(ERROR,
2184 (errcode(ERRCODE_CONNECTION_FAILURE),
2185 errmsg("could not drop replication slot \"%s\" on publisher: %s",
2186 slotname, res->err)));
2187 }
2188
2190 }
2191 PG_FINALLY();
2192 {
2193 pfree(cmd.data);
2194 }
2195 PG_END_TRY();
2196}
#define LOG
Definition: elog.h:31
const char * quote_identifier(const char *ident)
Definition: ruleutils.c:13030
@ WALRCV_OK_COMMAND
Definition: walreceiver.h:205
@ WALRCV_ERROR
Definition: walreceiver.h:204

References appendStringInfo(), Assert(), StringInfoData::data, ereport, WalRcvExecResult::err, errcode(), errmsg(), ERROR, initStringInfo(), load_file(), LOG, NOTICE, pfree(), PG_END_TRY, PG_FINALLY, PG_TRY, quote_identifier(), WalRcvExecResult::sqlstate, WalRcvExecResult::status, walrcv_clear_result(), WALRCV_ERROR, walrcv_exec, WALRCV_OK_COMMAND, and wrconn.

Referenced by AlterSubscription_refresh(), DropSubscription(), LogicalRepSyncTableStart(), and process_syncing_tables_for_sync().

◆ ReportSlotConnectionError()

static void ReportSlotConnectionError ( List rstates,
Oid  subid,
char *  slotname,
char *  err 
)
static

Definition at line 2712 of file subscriptioncmds.c.

2713{
2714 ListCell *lc;
2715
2716 foreach(lc, rstates)
2717 {
2719 Oid relid = rstate->relid;
2720
2721 /* Only cleanup resources of tablesync workers */
2722 if (!OidIsValid(relid))
2723 continue;
2724
2725 /*
2726 * Caller needs to ensure that relstate doesn't change underneath us.
2727 * See DropSubscription where we get the relstates.
2728 */
2729 if (rstate->state != SUBREL_STATE_SYNCDONE)
2730 {
2731 char syncslotname[NAMEDATALEN] = {0};
2732
2733 ReplicationSlotNameForTablesync(subid, relid, syncslotname,
2734 sizeof(syncslotname));
2735 elog(WARNING, "could not drop tablesync replication slot \"%s\"",
2736 syncslotname);
2737 }
2738 }
2739
2740 ereport(ERROR,
2741 (errcode(ERRCODE_CONNECTION_FAILURE),
2742 errmsg("could not connect to publisher when attempting to drop replication slot \"%s\": %s",
2743 slotname, err),
2744 /* translator: %s is an SQL ALTER command */
2745 errhint("Use %s to disable the subscription, and then use %s to disassociate it from the slot.",
2746 "ALTER SUBSCRIPTION ... DISABLE",
2747 "ALTER SUBSCRIPTION ... SET (slot_name = NONE)")));
2748}

References elog, ereport, err(), errcode(), errhint(), errmsg(), ERROR, lfirst, NAMEDATALEN, OidIsValid, SubscriptionRelState::relid, ReplicationSlotNameForTablesync(), SubscriptionRelState::state, and WARNING.

Referenced by DropSubscription().