PostgreSQL Source Code git master
Loading...
Searching...
No Matches
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_foreign_server.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_subscription_rel.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/subscriptioncmds.h"
#include "commands/tablecmds.h"
#include "executor/executor.h"
#include "foreign/foreign.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 "storage/lock.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
 
struct  PublicationRelKind
 

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_WAL_RECEIVER_TIMEOUT   0x00010000
 
#define SUBOPT_LSN   0x00020000
 
#define SUBOPT_ORIGIN   0x00040000
 
#define SUBOPT_CONFLICT_LOG_DEST   0x00080000
 
#define IsSet(val, bits)   (((val) & (bits)) == (bits))
 
#define appendQuotedIdentifier(b, s)   appendQuotedString(b, s, '"')
 
#define appendQuotedLiteral(b, s)   appendQuotedString(b, s, '\'')
 

Typedefs

typedef struct SubOpts SubOpts
 
typedef struct PublicationRelKind PublicationRelKind
 

Functions

static Listfetch_relation_list (WalReceiverConn *wrconn, List *publications)
 
static void check_publications_origin_tables (WalReceiverConn *wrconn, List *publications, bool copydata, bool retain_dead_tuples, char *origin, Oid *subrel_local_oids, int subrel_count, char *subname)
 
static void check_publications_origin_sequences (WalReceiverConn *wrconn, List *publications, bool copydata, 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 bool alter_sub_conflict_log_dest (Subscription *sub, ConflictLogDest oldlogdest, ConflictLogDest newlogdest, Oid *conflicttablerelid)
 
static void drop_sub_conflict_log_table (Oid subid, char *subname, Oid subconflictlogrelid)
 
static void parse_subscription_options (ParseState *pstate, List *stmt_options, uint32 supported_opts, SubOpts *opts)
 
static void appendQuotedString (StringInfo buf, const char *str, char quote)
 
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)
 
static void AlterSubscription_refresh_seq (Subscription *sub)
 
ObjectAddress AlterSubscription (ParseState *pstate, AlterSubscriptionStmt *stmt, bool isTopLevel)
 
static charconstruct_subserver_conninfo (Oid subserver, Oid subowner, char **err)
 
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)
 
static bool list_member_rangevar (const List *list, RangeVar *rv)
 
char defGetStreamingMode (DefElem *def)
 

Macro Definition Documentation

◆ appendQuotedIdentifier

#define appendQuotedIdentifier (   b,
 
)    appendQuotedString(b, s, '"')

Definition at line 568 of file subscriptioncmds.c.

◆ appendQuotedLiteral

#define appendQuotedLiteral (   b,
 
)    appendQuotedString(b, s, '\'')

Definition at line 569 of file subscriptioncmds.c.

◆ IsSet

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

Definition at line 88 of file subscriptioncmds.c.

◆ SUBOPT_BINARY

#define SUBOPT_BINARY   0x00000080

Definition at line 73 of file subscriptioncmds.c.

◆ SUBOPT_CONFLICT_LOG_DEST

#define SUBOPT_CONFLICT_LOG_DEST   0x00080000

Definition at line 85 of file subscriptioncmds.c.

◆ SUBOPT_CONNECT

#define SUBOPT_CONNECT   0x00000001

Definition at line 66 of file subscriptioncmds.c.

◆ SUBOPT_COPY_DATA

#define SUBOPT_COPY_DATA   0x00000010

Definition at line 70 of file subscriptioncmds.c.

◆ SUBOPT_CREATE_SLOT

#define SUBOPT_CREATE_SLOT   0x00000004

Definition at line 68 of file subscriptioncmds.c.

◆ SUBOPT_DISABLE_ON_ERR

#define SUBOPT_DISABLE_ON_ERR   0x00000400

Definition at line 76 of file subscriptioncmds.c.

◆ SUBOPT_ENABLED

#define SUBOPT_ENABLED   0x00000002

Definition at line 67 of file subscriptioncmds.c.

◆ SUBOPT_FAILOVER

#define SUBOPT_FAILOVER   0x00002000

Definition at line 79 of file subscriptioncmds.c.

◆ SUBOPT_LSN

#define SUBOPT_LSN   0x00020000

Definition at line 83 of file subscriptioncmds.c.

◆ SUBOPT_MAX_RETENTION_DURATION

#define SUBOPT_MAX_RETENTION_DURATION   0x00008000

Definition at line 81 of file subscriptioncmds.c.

◆ SUBOPT_ORIGIN

#define SUBOPT_ORIGIN   0x00040000

Definition at line 84 of file subscriptioncmds.c.

◆ SUBOPT_PASSWORD_REQUIRED

#define SUBOPT_PASSWORD_REQUIRED   0x00000800

Definition at line 77 of file subscriptioncmds.c.

◆ SUBOPT_REFRESH

#define SUBOPT_REFRESH   0x00000040

Definition at line 72 of file subscriptioncmds.c.

◆ SUBOPT_RETAIN_DEAD_TUPLES

#define SUBOPT_RETAIN_DEAD_TUPLES   0x00004000

Definition at line 80 of file subscriptioncmds.c.

◆ SUBOPT_RUN_AS_OWNER

#define SUBOPT_RUN_AS_OWNER   0x00001000

Definition at line 78 of file subscriptioncmds.c.

◆ SUBOPT_SLOT_NAME

#define SUBOPT_SLOT_NAME   0x00000008

Definition at line 69 of file subscriptioncmds.c.

◆ SUBOPT_STREAMING

#define SUBOPT_STREAMING   0x00000100

Definition at line 74 of file subscriptioncmds.c.

◆ SUBOPT_SYNCHRONOUS_COMMIT

#define SUBOPT_SYNCHRONOUS_COMMIT   0x00000020

Definition at line 71 of file subscriptioncmds.c.

◆ SUBOPT_TWOPHASE_COMMIT

#define SUBOPT_TWOPHASE_COMMIT   0x00000200

Definition at line 75 of file subscriptioncmds.c.

◆ SUBOPT_WAL_RECEIVER_TIMEOUT

#define SUBOPT_WAL_RECEIVER_TIMEOUT   0x00010000

Definition at line 82 of file subscriptioncmds.c.

Typedef Documentation

◆ PublicationRelKind

◆ SubOpts

Function Documentation

◆ alter_sub_conflict_log_dest()

static bool alter_sub_conflict_log_dest ( Subscription sub,
ConflictLogDest  oldlogdest,
ConflictLogDest  newlogdest,
Oid conflicttablerelid 
)
static

Definition at line 1549 of file subscriptioncmds.c.

1552{
1553 bool want_table;
1554 bool has_oldtable;
1555 bool update_relid = false;
1556 Oid relid = InvalidOid;
1557
1560
1561 if (has_oldtable)
1562 {
1563 /* There is a conflict log table already. */
1564 if (!want_table)
1565 {
1567 sub->conflictlogrelid);
1568 update_relid = true;
1569 }
1570 }
1571 else
1572 {
1573 /* There was no previous conflict log table. */
1574 if (want_table)
1575 {
1578
1579 relid = create_conflict_log_table(sub->oid, sub->name, sub->owner);
1580 update_relid = true;
1581
1582 /*
1583 * Establish an internal dependency between the conflict log table
1584 * and the subscription. For details refer comments in
1585 * CreateSubscription function.
1586 */
1590 }
1591 }
1592
1593 *conflicttablerelid = relid;
1594 return update_relid;
1595}
Oid create_conflict_log_table(Oid subid, char *subname, Oid subowner)
Definition conflict.c:147
#define CONFLICTS_LOGGED_TO_TABLE(dest)
Definition conflict.h:96
@ DEPENDENCY_INTERNAL
Definition dependency.h:35
#define ObjectAddressSet(addr, class_id, object_id)
void recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior)
Definition pg_depend.c:51
#define InvalidOid
unsigned int Oid
static int fb(int x)
static void drop_sub_conflict_log_table(Oid subid, char *subname, Oid subconflictlogrelid)

References Subscription::conflictlogrelid, CONFLICTS_LOGGED_TO_TABLE, create_conflict_log_table(), DEPENDENCY_INTERNAL, drop_sub_conflict_log_table(), fb(), InvalidOid, Subscription::name, ObjectAddressSet, Subscription::oid, Subscription::owner, and recordDependencyOn().

Referenced by AlterSubscription().

◆ AlterSubscription()

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

Definition at line 1601 of file subscriptioncmds.c.

1603{
1604 Relation rel;
1606 bool nulls[Natts_pg_subscription];
1609 HeapTuple tup;
1610 Oid subid;
1611 bool orig_conninfo_needed = true;
1612 bool update_tuple = false;
1613 bool update_failover = false;
1614 bool update_two_phase = false;
1615 bool check_pub_rdt = false;
1616 bool retain_dead_tuples;
1617 int max_retention;
1618 bool retention_active;
1619 char *new_conninfo = NULL;
1620 char *origin;
1621 Subscription *sub;
1624 SubOpts opts = {0};
1625
1627
1628 /* Fetch the existing tuple. */
1630 CStringGetDatum(stmt->subname));
1631
1632 if (!HeapTupleIsValid(tup))
1633 ereport(ERROR,
1635 errmsg("subscription \"%s\" does not exist",
1636 stmt->subname)));
1637
1639 subid = form->oid;
1640
1641 /* must be owner */
1644 stmt->subname);
1645
1646 /* parse and check options */
1647 switch (stmt->kind)
1648 {
1661 break;
1662
1665 break;
1666
1669 break;
1670
1674 break;
1675
1678 break;
1679
1682 break;
1683
1684 default:
1685 supported_opts = 0;
1686 break;
1687 }
1688
1689 if (supported_opts > 0)
1691
1692 /*
1693 * Ensure that ALTER SUBSCRIPTION commands that could be used to fix a
1694 * broken connection or prepare to drop a broken subscription don't
1695 * attempt to construct the conninfo. Otherwise, we might encounter the
1696 * error the user is trying to fix.
1697 *
1698 * Specifically, ALTER SUBSCRIPTION DISABLE, ALTER SUBSCRIPTION SERVER,
1699 * ALTER SUBSCRIPTION CONNECTION, or ALTER SUBSCRIPTION SET
1700 * (slot_name=NONE).
1701 *
1702 * NB: if the user specifies multiple SET options, then we may still need
1703 * to construct conninfo even if slot_name is set to NONE.
1704 */
1705 if (stmt->kind == ALTER_SUBSCRIPTION_ENABLED)
1706 {
1707 if (opts.specified_opts == SUBOPT_ENABLED && !opts.enabled)
1708 orig_conninfo_needed = false;
1709 }
1710 else if (stmt->kind == ALTER_SUBSCRIPTION_SERVER ||
1712 {
1713 orig_conninfo_needed = false;
1714 }
1715 else if (stmt->kind == ALTER_SUBSCRIPTION_OPTIONS)
1716 {
1717 /* ... SET (slot_name = NONE) with no other options */
1718 if (opts.specified_opts == SUBOPT_SLOT_NAME && !opts.slot_name)
1719 orig_conninfo_needed = false;
1720 }
1721
1722 /*
1723 * Skip ACL checks on the subscription's foreign server, if any. If
1724 * changing the server (or replacing it with a raw connection), then the
1725 * old one will be removed anyway. If changing something unrelated,
1726 * there's no need to do an additional ACL check here; that will be done
1727 * by the subscription worker.
1728 */
1729 sub = GetSubscription(subid, false, orig_conninfo_needed, false);
1730
1732 origin = sub->origin;
1735
1736 /*
1737 * Don't allow non-superuser modification of a subscription with
1738 * password_required=false.
1739 */
1740 if (!sub->passwordrequired && !superuser())
1741 ereport(ERROR,
1743 errmsg("password_required=false is superuser-only"),
1744 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1745
1746 /* Lock the subscription so nobody else can do anything with it. */
1748
1749 /* Form a new tuple. */
1750 memset(values, 0, sizeof(values));
1751 memset(nulls, false, sizeof(nulls));
1752 memset(replaces, false, sizeof(replaces));
1753
1755
1756 switch (stmt->kind)
1757 {
1759 {
1760 if (IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1761 {
1762 /*
1763 * The subscription must be disabled to allow slot_name as
1764 * 'none', otherwise, the apply worker will repeatedly try
1765 * to stream the data using that slot_name which neither
1766 * exists on the publisher nor the user will be allowed to
1767 * create it.
1768 */
1769 if (sub->enabled && !opts.slot_name)
1770 ereport(ERROR,
1772 errmsg("cannot set %s for enabled subscription",
1773 "slot_name = NONE")));
1774
1775 if (opts.slot_name)
1778 else
1779 nulls[Anum_pg_subscription_subslotname - 1] = true;
1781 }
1782
1783 if (opts.synchronous_commit)
1784 {
1786 CStringGetTextDatum(opts.synchronous_commit);
1788 }
1789
1790 if (IsSet(opts.specified_opts, SUBOPT_BINARY))
1791 {
1793 BoolGetDatum(opts.binary);
1795 }
1796
1797 if (IsSet(opts.specified_opts, SUBOPT_STREAMING))
1798 {
1800 CharGetDatum(opts.streaming);
1802 }
1803
1804 if (IsSet(opts.specified_opts, SUBOPT_DISABLE_ON_ERR))
1805 {
1807 = BoolGetDatum(opts.disableonerr);
1809 = true;
1810 }
1811
1812 if (IsSet(opts.specified_opts, SUBOPT_PASSWORD_REQUIRED))
1813 {
1814 /* Non-superuser may not disable password_required. */
1815 if (!opts.passwordrequired && !superuser())
1816 ereport(ERROR,
1818 errmsg("password_required=false is superuser-only"),
1819 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1820
1822 = BoolGetDatum(opts.passwordrequired);
1824 = true;
1825 }
1826
1827 if (IsSet(opts.specified_opts, SUBOPT_RUN_AS_OWNER))
1828 {
1830 BoolGetDatum(opts.runasowner);
1832 }
1833
1834 if (IsSet(opts.specified_opts, SUBOPT_TWOPHASE_COMMIT))
1835 {
1836 /*
1837 * We need to update both the slot and the subscription
1838 * for the two_phase option. We can enable the two_phase
1839 * option for a slot only once the initial data
1840 * synchronization is done. This is to avoid missing some
1841 * data as explained in comments atop worker.c.
1842 */
1843 update_two_phase = !opts.twophase;
1844
1845 CheckAlterSubOption(sub, "two_phase", update_two_phase,
1846 isTopLevel);
1847
1848 /*
1849 * Modifying the two_phase slot option requires a slot
1850 * lookup by slot name, so changing the slot name at the
1851 * same time is not allowed.
1852 */
1853 if (update_two_phase &&
1854 IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1855 ereport(ERROR,
1857 errmsg("\"slot_name\" and \"two_phase\" cannot be altered at the same time")));
1858
1859 /*
1860 * Note that workers may still survive even if the
1861 * subscription has been disabled.
1862 *
1863 * Ensure workers have already been exited to avoid
1864 * getting prepared transactions while we are disabling
1865 * the two_phase option. Otherwise, the changes of an
1866 * already prepared transaction can be replicated again
1867 * along with its corresponding commit, leading to
1868 * duplicate data or errors.
1869 */
1870 if (logicalrep_workers_find(subid, true, true))
1871 ereport(ERROR,
1873 errmsg("cannot alter \"two_phase\" when logical replication worker is still running"),
1874 errhint("Try again after some time.")));
1875
1876 /*
1877 * two_phase cannot be disabled if there are any
1878 * uncommitted prepared transactions present otherwise it
1879 * can lead to duplicate data or errors as explained in
1880 * the comment above.
1881 */
1882 if (update_two_phase &&
1884 LookupGXactBySubid(subid))
1885 ereport(ERROR,
1887 errmsg("cannot disable \"two_phase\" when prepared transactions exist"),
1888 errhint("Resolve these transactions and try again.")));
1889
1890 /* Change system catalog accordingly */
1892 CharGetDatum(opts.twophase ?
1896 }
1897
1898 if (IsSet(opts.specified_opts, SUBOPT_FAILOVER))
1899 {
1900 /*
1901 * Similar to the two_phase case above, we need to update
1902 * the failover option for both the slot and the
1903 * subscription.
1904 */
1905 update_failover = true;
1906
1907 CheckAlterSubOption(sub, "failover", update_failover,
1908 isTopLevel);
1909
1911 BoolGetDatum(opts.failover);
1913 }
1914
1915 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES))
1916 {
1918 BoolGetDatum(opts.retaindeadtuples);
1920
1921 /*
1922 * Update the retention status only if there's a change in
1923 * the retain_dead_tuples option value.
1924 *
1925 * Automatically marking retention as active when
1926 * retain_dead_tuples is enabled may not always be ideal,
1927 * especially if retention was previously stopped and the
1928 * user toggles retain_dead_tuples without adjusting the
1929 * publisher workload. However, this behavior provides a
1930 * convenient way for users to manually refresh the
1931 * retention status. Since retention will be stopped again
1932 * unless the publisher workload is reduced, this approach
1933 * is acceptable for now.
1934 */
1935 if (opts.retaindeadtuples != sub->retaindeadtuples)
1936 {
1938 BoolGetDatum(opts.retaindeadtuples);
1940
1941 retention_active = opts.retaindeadtuples;
1942 }
1943
1944 CheckAlterSubOption(sub, "retain_dead_tuples", false, isTopLevel);
1945
1946 /*
1947 * Workers may continue running even after the
1948 * subscription has been disabled.
1949 *
1950 * To prevent race conditions (as described in
1951 * CheckAlterSubOption()), ensure that all worker
1952 * processes have already exited before proceeding.
1953 */
1954 if (logicalrep_workers_find(subid, true, true))
1955 ereport(ERROR,
1957 errmsg("cannot alter retain_dead_tuples when logical replication worker is still running"),
1958 errhint("Try again after some time.")));
1959
1960 /*
1961 * Notify the launcher to manage the replication slot for
1962 * conflict detection. This ensures that replication slot
1963 * is efficiently handled (created, updated, or dropped)
1964 * in response to any configuration changes.
1965 */
1967
1968 check_pub_rdt = opts.retaindeadtuples;
1969 retain_dead_tuples = opts.retaindeadtuples;
1970 }
1971
1972 if (IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
1973 {
1975 Int32GetDatum(opts.maxretention);
1977
1978 max_retention = opts.maxretention;
1979 }
1980
1981 /*
1982 * Ensure that system configuration parameters are set
1983 * appropriately to support retain_dead_tuples and
1984 * max_retention_duration.
1985 */
1986 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES) ||
1987 IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
1991 (max_retention > 0));
1992
1993 if (IsSet(opts.specified_opts, SUBOPT_ORIGIN))
1994 {
1996 CStringGetTextDatum(opts.origin);
1998
1999 /*
2000 * Check if changes from different origins may be received
2001 * from the publisher when the origin is changed to ANY
2002 * and retain_dead_tuples is enabled. Use |= so that we
2003 * don't clear the flag already set when
2004 * retain_dead_tuples was changed in the same command.
2005 */
2008
2009 origin = opts.origin;
2010 }
2011
2012 if (IsSet(opts.specified_opts, SUBOPT_WAL_RECEIVER_TIMEOUT))
2013 {
2015 CStringGetTextDatum(opts.wal_receiver_timeout);
2017 }
2018
2019 if (IsSet(opts.specified_opts, SUBOPT_CONFLICT_LOG_DEST))
2020 {
2023
2024 if (opts.conflictlogdest != old_dest)
2025 {
2026 bool update_relid;
2027 Oid relid = InvalidOid;
2028
2032
2034 old_dest,
2035 opts.conflictlogdest,
2036 &relid);
2037 if (update_relid)
2038 {
2040 ObjectIdGetDatum(relid);
2042 true;
2043 }
2044 }
2045 }
2046
2047 update_tuple = true;
2048 break;
2049 }
2050
2052 {
2053 Assert(IsSet(opts.specified_opts, SUBOPT_ENABLED));
2054
2055 if (!sub->slotname && opts.enabled)
2056 ereport(ERROR,
2058 errmsg("cannot enable subscription that does not have a slot name")));
2059
2060 /*
2061 * Check track_commit_timestamp only when enabling the
2062 * subscription in case it was disabled after creation. See
2063 * comments atop CheckSubDeadTupleRetention() for details.
2064 */
2065 CheckSubDeadTupleRetention(opts.enabled, !opts.enabled,
2067 sub->retentionactive, false);
2068
2070 BoolGetDatum(opts.enabled);
2072
2073 if (opts.enabled)
2075
2076 update_tuple = true;
2077
2078 /*
2079 * The subscription might be initially created with
2080 * connect=false and retain_dead_tuples=true, meaning the
2081 * remote server's status may not be checked. Ensure this
2082 * check is conducted now.
2083 */
2084 check_pub_rdt = sub->retaindeadtuples && opts.enabled;
2085 break;
2086 }
2087
2089 {
2093
2094 /*
2095 * Remove what was there before, either another foreign server
2096 * or a connection string.
2097 */
2098 if (form->subserver)
2099 {
2102 ForeignServerRelationId, form->subserver);
2103 }
2104 else
2105 {
2106 nulls[Anum_pg_subscription_subconninfo - 1] = true;
2108 }
2109
2110 /*
2111 * Check that the subscription owner has USAGE privileges on
2112 * the server.
2113 */
2114 new_server = GetForeignServerByName(stmt->servername, false);
2116 new_server->serverid,
2117 form->subowner, ACL_USAGE);
2118 if (aclresult != ACLCHECK_OK)
2119 ereport(ERROR,
2121 errmsg("subscription owner \"%s\" does not have permission on foreign server \"%s\"",
2122 GetUserNameFromId(form->subowner, false),
2123 new_server->servername));
2124
2125 /* make sure a user mapping exists */
2126 GetUserMapping(form->subowner, new_server->serverid);
2127
2129 new_server);
2130
2131 /* Load the library providing us libpq calls. */
2132 load_file("libpqwalreceiver", false);
2133 /* Check the connection info string. */
2135 sub->passwordrequired && !sub->ownersuperuser);
2136
2139
2142
2143 update_tuple = true;
2144 }
2145
2146 /*
2147 * Since the remote server configuration might have changed,
2148 * perform a check to ensure it permits enabling
2149 * retain_dead_tuples.
2150 */
2152 break;
2153
2155 /* remove reference to foreign server and dependencies, if present */
2156 if (form->subserver)
2157 {
2160 ForeignServerRelationId, form->subserver);
2161
2164 }
2165
2166 new_conninfo = stmt->conninfo;
2167
2168 /* Load the library providing us libpq calls. */
2169 load_file("libpqwalreceiver", false);
2170 /* Check the connection info string. */
2172 sub->passwordrequired && !sub->ownersuperuser);
2173
2175 CStringGetTextDatum(stmt->conninfo);
2177 update_tuple = true;
2178
2179 /*
2180 * Since the remote server configuration might have changed,
2181 * perform a check to ensure it permits enabling
2182 * retain_dead_tuples.
2183 */
2185 break;
2186
2188 {
2190 publicationListToArray(stmt->publication);
2192
2193 update_tuple = true;
2194
2195 /* Refresh if user asked us to. */
2196 if (opts.refresh)
2197 {
2198 if (!sub->enabled)
2199 ereport(ERROR,
2201 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
2202 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
2203
2204 /*
2205 * See ALTER_SUBSCRIPTION_REFRESH_PUBLICATION for details
2206 * why this is not allowed.
2207 */
2208 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2209 ereport(ERROR,
2211 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
2212 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
2213
2214 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
2215
2216 /* Make sure refresh sees the new list of publications. */
2217 sub->publications = stmt->publication;
2218
2219 AlterSubscription_refresh(sub, opts.copy_data,
2220 stmt->publication);
2221 }
2222
2223 break;
2224 }
2225
2228 {
2229 List *publist;
2231
2232 publist = merge_publications(sub->publications, stmt->publication, isadd, stmt->subname);
2236
2237 update_tuple = true;
2238
2239 /* Refresh if user asked us to. */
2240 if (opts.refresh)
2241 {
2242 /* We only need to validate user specified publications. */
2243 List *validate_publications = (isadd) ? stmt->publication : NULL;
2244
2245 if (!sub->enabled)
2246 ereport(ERROR,
2248 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
2249 /* translator: %s is an SQL ALTER command */
2250 errhint("Use %s instead.",
2251 isadd ?
2252 "ALTER SUBSCRIPTION ... ADD PUBLICATION ... WITH (refresh = false)" :
2253 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
2254
2255 /*
2256 * See ALTER_SUBSCRIPTION_REFRESH_PUBLICATION for details
2257 * why this is not allowed.
2258 */
2259 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2260 ereport(ERROR,
2262 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
2263 /* translator: %s is an SQL ALTER command */
2264 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
2265 isadd ?
2266 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
2267 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
2268
2269 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
2270
2271 /* Refresh the new list of publications. */
2272 sub->publications = publist;
2273
2274 AlterSubscription_refresh(sub, opts.copy_data,
2276 }
2277
2278 break;
2279 }
2280
2282 {
2283 if (!sub->enabled)
2284 ereport(ERROR,
2286 errmsg("%s is not allowed for disabled subscriptions",
2287 "ALTER SUBSCRIPTION ... REFRESH PUBLICATION")));
2288
2289 /*
2290 * The subscription option "two_phase" requires that
2291 * replication has passed the initial table synchronization
2292 * phase before the two_phase becomes properly enabled.
2293 *
2294 * But, having reached this two-phase commit "enabled" state
2295 * we must not allow any subsequent table initialization to
2296 * occur. So the ALTER SUBSCRIPTION ... REFRESH PUBLICATION is
2297 * disallowed when the user had requested two_phase = on mode.
2298 *
2299 * The exception to this restriction is when copy_data =
2300 * false, because when copy_data is false the tablesync will
2301 * start already in READY state and will exit directly without
2302 * doing anything.
2303 *
2304 * For more details see comments atop worker.c.
2305 */
2306 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2307 ereport(ERROR,
2309 errmsg("ALTER SUBSCRIPTION ... REFRESH PUBLICATION with copy_data is not allowed when two_phase is enabled"),
2310 errhint("Use ALTER SUBSCRIPTION ... REFRESH PUBLICATION with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
2311
2312 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH PUBLICATION");
2313
2314 AlterSubscription_refresh(sub, opts.copy_data, NULL);
2315
2316 break;
2317 }
2318
2320 {
2321 if (!sub->enabled)
2322 ereport(ERROR,
2324 errmsg("%s is not allowed for disabled subscriptions",
2325 "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"));
2326
2328
2329 break;
2330 }
2331
2333 {
2334 /* ALTER SUBSCRIPTION ... SKIP supports only LSN option */
2335 Assert(IsSet(opts.specified_opts, SUBOPT_LSN));
2336
2337 /*
2338 * If the user sets subskiplsn, we do a sanity check to make
2339 * sure that the specified LSN is a probable value.
2340 */
2341 if (XLogRecPtrIsValid(opts.lsn))
2342 {
2344 char originname[NAMEDATALEN];
2345 XLogRecPtr remote_lsn;
2346
2348 originname, sizeof(originname));
2350 remote_lsn = replorigin_get_progress(originid, false);
2351
2352 /* Check the given LSN is at least a future LSN */
2353 if (XLogRecPtrIsValid(remote_lsn) && opts.lsn < remote_lsn)
2354 ereport(ERROR,
2356 errmsg("skip WAL location (LSN %X/%08X) must be greater than origin LSN %X/%08X",
2357 LSN_FORMAT_ARGS(opts.lsn),
2358 LSN_FORMAT_ARGS(remote_lsn))));
2359 }
2360
2363
2364 update_tuple = true;
2365 break;
2366 }
2367
2368 default:
2369 elog(ERROR, "unrecognized ALTER SUBSCRIPTION kind %d",
2370 stmt->kind);
2371 }
2372
2373 /* Update the catalog if needed. */
2374 if (update_tuple)
2375 {
2377 replaces);
2378
2379 CatalogTupleUpdate(rel, &tup->t_self, tup);
2380
2382 }
2383
2384 /*
2385 * Try to acquire the connection necessary either for modifying the slot
2386 * or for checking if the remote server permits enabling
2387 * retain_dead_tuples.
2388 *
2389 * This has to be at the end because otherwise if there is an error while
2390 * doing the database operations we won't be able to rollback altered
2391 * slot.
2392 */
2394 {
2395 bool must_use_password;
2396 char *err;
2398
2400
2401 /* Load the library providing us libpq calls. */
2402 load_file("libpqwalreceiver", false);
2403
2404 /*
2405 * Try to connect to the publisher, using the new connection string if
2406 * available.
2407 */
2409 wrconn = walrcv_connect(new_conninfo ? new_conninfo : sub->conninfo,
2411 &err);
2412 if (!wrconn)
2413 ereport(ERROR,
2415 errmsg("subscription \"%s\" could not connect to the publisher: %s",
2416 sub->name, err)));
2417
2418 PG_TRY();
2419 {
2422
2424 retain_dead_tuples, origin, NULL, 0,
2425 sub->name);
2426
2429 update_failover ? &opts.failover : NULL,
2430 update_two_phase ? &opts.twophase : NULL);
2431 }
2432 PG_FINALLY();
2433 {
2435 }
2436 PG_END_TRY();
2437 }
2438
2440
2442
2443 /* Wake up related replication workers to handle this change quickly. */
2445
2446 return myself;
2447}
AclResult
Definition acl.h:183
@ ACLCHECK_OK
Definition acl.h:184
@ ACLCHECK_NOT_OWNER
Definition acl.h:186
void aclcheck_error(AclResult aclerr, ObjectType objtype, const char *objectname)
Definition aclchk.c:2672
AclResult object_aclcheck(Oid classid, Oid objectid, Oid roleid, AclMode mode)
Definition aclchk.c:3902
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
Definition aclchk.c:4156
void LogicalRepWorkersWakeupAtCommit(Oid subid)
Definition worker.c:6342
void ReplicationOriginNameForLogicalRep(Oid suboid, Oid relid, char *originname, Size szoriginname)
Definition worker.c:648
static Datum values[MAXATTR]
Definition bootstrap.c:190
#define CStringGetTextDatum(s)
Definition builtins.h:98
#define Assert(condition)
Definition c.h:1002
uint32_t uint32
Definition c.h:683
const char *const ConflictLogDestNames[]
Definition conflict.c:34
ConflictLogDest GetConflictLogDest(const char *dest)
Definition conflict.c:210
ConflictLogDest
Definition conflict.h:90
@ DEPENDENCY_NORMAL
Definition dependency.h:33
void load_file(const char *filename, bool restricted)
Definition dfmgr.c:149
int errcode(int sqlerrcode)
Definition elog.c:875
int errhint(const char *fmt,...) pg_attribute_printf(1
#define PG_TRY(...)
Definition elog.h:374
#define WARNING
Definition elog.h:37
#define PG_END_TRY(...)
Definition elog.h:399
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define NOTICE
Definition elog.h:36
#define PG_FINALLY(...)
Definition elog.h:391
#define ereport(elevel,...)
Definition elog.h:152
void err(int eval, const char *fmt,...)
Definition err.c:43
#define DirectFunctionCall1(func, arg1)
Definition fmgr.h:688
ForeignServer * GetForeignServerByName(const char *srvname, bool missing_ok)
Definition foreign.c:185
char * ForeignServerConnectionString(Oid userid, ForeignServer *server)
Definition foreign.c:202
UserMapping * GetUserMapping(Oid userid, Oid serverid)
Definition foreign.c:232
Oid MyDatabaseId
Definition globals.c:96
HeapTuple heap_modify_tuple(HeapTuple tuple, TupleDesc tupleDesc, const Datum *replValues, const bool *replIsnull, const bool *doReplace)
Definition heaptuple.c:1118
void heap_freetuple(HeapTuple htup)
Definition heaptuple.c:1372
#define HeapTupleIsValid(tuple)
Definition htup.h:78
static void * GETSTRUCT(const HeapTupleData *tuple)
#define stmt
void CatalogTupleUpdate(Relation heapRel, const ItemPointerData *otid, HeapTuple tup)
Definition indexing.c:313
return true
Definition isn.c:130
List * logicalrep_workers_find(Oid subid, bool only_running, bool acquire_lock)
Definition launcher.c:303
void ApplyLauncherWakeupAtCommit(void)
Definition launcher.c:1185
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:470
char * GetUserNameFromId(Oid roleid, bool noerr)
Definition miscinit.c:990
Datum namein(PG_FUNCTION_ARGS)
Definition name.c:48
static char * errmsg
#define InvokeObjectPostAlterHook(classId, objectId, subId)
ReplOriginId replorigin_by_name(const char *roname, bool missing_ok)
Definition origin.c:243
XLogRecPtr replorigin_get_progress(ReplOriginId node, bool flush)
Definition origin.c:1057
@ ALTER_SUBSCRIPTION_REFRESH_PUBLICATION
@ ALTER_SUBSCRIPTION_ENABLED
@ ALTER_SUBSCRIPTION_DROP_PUBLICATION
@ ALTER_SUBSCRIPTION_SERVER
@ ALTER_SUBSCRIPTION_SET_PUBLICATION
@ ALTER_SUBSCRIPTION_REFRESH_SEQUENCES
@ ALTER_SUBSCRIPTION_SKIP
@ ALTER_SUBSCRIPTION_OPTIONS
@ ALTER_SUBSCRIPTION_CONNECTION
@ ALTER_SUBSCRIPTION_ADD_PUBLICATION
#define ACL_USAGE
Definition parsenodes.h:84
@ OBJECT_SUBSCRIPTION
static AmcheckOptions opts
Definition pg_amcheck.c:112
#define NAMEDATALEN
long deleteDependencyRecordsForSpecific(Oid classId, Oid objectId, char deptype, Oid refclassId, Oid refobjectId)
Definition pg_depend.c:411
static Datum LSNGetDatum(XLogRecPtr X)
Definition pg_lsn.h:31
Subscription * GetSubscription(Oid subid, bool missing_ok, bool conninfo_needed, bool conninfo_aclcheck)
END_CATALOG_STRUCT typedef FormData_pg_subscription * Form_pg_subscription
int pg_strcasecmp(const char *s1, const char *s2)
static Datum BoolGetDatum(bool X)
Definition postgres.h:112
static Datum ObjectIdGetDatum(Oid X)
Definition postgres.h:252
uint64_t Datum
Definition postgres.h:70
static Datum CStringGetDatum(const char *X)
Definition postgres.h:383
static Datum Int32GetDatum(int32 X)
Definition postgres.h:212
static Datum CharGetDatum(char X)
Definition postgres.h:132
#define RelationGetDescr(relation)
Definition rel.h:542
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 bool alter_sub_conflict_log_dest(Subscription *sub, ConflictLogDest oldlogdest, ConflictLogDest newlogdest, Oid *conflicttablerelid)
#define SUBOPT_RUN_AS_OWNER
#define SUBOPT_SLOT_NAME
#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
#define SUBOPT_CONFLICT_LOG_DEST
static void parse_subscription_options(ParseState *pstate, List *stmt_options, uint32 supported_opts, SubOpts *opts)
void CheckSubDeadTupleRetention(bool check_guc, bool sub_disabled, int elevel_for_sub_disabled, bool retain_dead_tuples, bool retention_active, bool max_retention_set)
static void AlterSubscription_refresh_seq(Subscription *sub)
#define SUBOPT_LSN
#define SUBOPT_MAX_RETENTION_DURATION
static List * merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname)
#define SUBOPT_WAL_RECEIVER_TIMEOUT
static void check_publications_origin_tables(WalReceiverConn *wrconn, List *publications, bool copydata, bool retain_dead_tuples, char *origin, Oid *subrel_local_oids, int subrel_count, 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:47
#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:2805
const char * name
static WalReceiverConn * wrconn
Definition walreceiver.c:95
#define walrcv_connect(conninfo, replication, logical, must_use_password, appname, err)
#define walrcv_check_conninfo(conninfo, must_use_password)
#define walrcv_alter_slot(conn, slotname, failover, two_phase)
#define walrcv_disconnect(conn)
void PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
Definition xact.c:3701
#define XLogRecPtrIsValid(r)
Definition xlogdefs.h:29
#define LSN_FORMAT_ARGS(lsn)
Definition xlogdefs.h:47
uint16 ReplOriginId
Definition xlogdefs.h:69
uint64 XLogRecPtr
Definition xlogdefs.h:21

References AccessExclusiveLock, ACL_USAGE, aclcheck_error(), ACLCHECK_NOT_OWNER, ACLCHECK_OK, alter_sub_conflict_log_dest(), ALTER_SUBSCRIPTION_ADD_PUBLICATION, ALTER_SUBSCRIPTION_CONNECTION, ALTER_SUBSCRIPTION_DROP_PUBLICATION, ALTER_SUBSCRIPTION_ENABLED, ALTER_SUBSCRIPTION_OPTIONS, ALTER_SUBSCRIPTION_REFRESH_PUBLICATION, ALTER_SUBSCRIPTION_REFRESH_SEQUENCES, ALTER_SUBSCRIPTION_SERVER, ALTER_SUBSCRIPTION_SET_PUBLICATION, ALTER_SUBSCRIPTION_SKIP, AlterSubscription_refresh(), AlterSubscription_refresh_seq(), ApplyLauncherWakeupAtCommit(), Assert, BoolGetDatum(), CatalogTupleUpdate(), CharGetDatum(), check_pub_dead_tuple_retention(), check_publications_origin_tables(), CheckAlterSubOption(), CheckSubDeadTupleRetention(), Subscription::conflictlogdest, ConflictLogDestNames, Subscription::conninfo, CStringGetDatum(), CStringGetTextDatum, deleteDependencyRecordsForSpecific(), DEPENDENCY_NORMAL, DirectFunctionCall1, elog, Subscription::enabled, ereport, err(), errcode(), errhint(), errmsg, ERROR, fb(), ForeignServerConnectionString(), Form_pg_subscription, GetConflictLogDest(), GetForeignServerByName(), GETSTRUCT(), GetSubscription(), GetUserId(), GetUserMapping(), GetUserNameFromId(), heap_freetuple(), heap_modify_tuple(), HeapTupleIsValid, 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_aclcheck(), 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, recordDependencyOn(), RelationGetDescr, ReplicationOriginNameForLogicalRep(), replorigin_by_name(), replorigin_get_progress(), Subscription::retaindeadtuples, Subscription::retentionactive, RowExclusiveLock, SearchSysCacheCopy2, Subscription::slotname, stmt, SUBOPT_BINARY, SUBOPT_CONFLICT_LOG_DEST, 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, SUBOPT_WAL_RECEIVER_TIMEOUT, superuser(), table_close(), table_open(), Subscription::twophasestate, values, walrcv_alter_slot, walrcv_check_conninfo, walrcv_connect, walrcv_disconnect, WARNING, wrconn, and XLogRecPtrIsValid.

Referenced by ProcessUtilitySlow().

◆ AlterSubscription_refresh()

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

Definition at line 1090 of file subscriptioncmds.c.

1092{
1093 char *err;
1094 List *pubrels = NIL;
1100 int subrel_count;
1101 ListCell *lc;
1102 int off;
1103 int tbl_count = 0;
1104 int seq_count = 0;
1105 Relation rel = NULL;
1106 typedef struct SubRemoveRels
1107 {
1108 Oid relid;
1109 char state;
1110 } SubRemoveRels;
1111
1113 bool must_use_password;
1114
1115 /* Load the library providing us libpq calls. */
1116 load_file("libpqwalreceiver", false);
1117
1118 /* Try to connect to the publisher. */
1120 wrconn = walrcv_connect(sub->conninfo, true, true, must_use_password,
1121 sub->name, &err);
1122 if (!wrconn)
1123 ereport(ERROR,
1125 errmsg("subscription \"%s\" could not connect to the publisher: %s",
1126 sub->name, err)));
1127
1128 PG_TRY();
1129 {
1132
1133 /* Get the relation list from publisher. */
1135
1136 /* Get local relation list. */
1137 subrel_states = GetSubscriptionRelations(sub->oid, true, true, false);
1139
1140 /*
1141 * Build qsorted arrays of local table oids and sequence oids for
1142 * faster lookup. This can potentially contain all tables and
1143 * sequences in the database so speed of lookup is important.
1144 *
1145 * We do not yet know the exact count of tables and sequences, so we
1146 * allocate separate arrays for table OIDs and sequence OIDs based on
1147 * the total number of relations (subrel_count).
1148 */
1151 foreach(lc, subrel_states)
1152 {
1154
1155 if (get_rel_relkind(relstate->relid) == RELKIND_SEQUENCE)
1156 subseq_local_oids[seq_count++] = relstate->relid;
1157 else
1158 subrel_local_oids[tbl_count++] = relstate->relid;
1159 }
1160
1163 sub->retaindeadtuples, sub->origin,
1165 sub->name);
1166
1169 copy_data, sub->origin,
1171 sub->name);
1172
1173 /*
1174 * Walk over the remote relations and try to match them to locally
1175 * known relations. If the relation is not known locally create a new
1176 * state for it.
1177 *
1178 * Also builds array of local oids of remote relations for the next
1179 * step.
1180 */
1181 off = 0;
1183
1185 {
1186 RangeVar *rv = pubrelinfo->rv;
1187 Oid relid;
1188 char relkind;
1189
1190 relid = RangeVarGetRelid(rv, AccessShareLock, false);
1191 relkind = get_rel_relkind(relid);
1192
1193 /* Check for supported relkind. */
1194 CheckSubscriptionRelkind(relkind, pubrelinfo->relkind,
1195 rv->schemaname, rv->relname);
1196
1197 pubrel_local_oids[off++] = relid;
1198
1199 if (!bsearch(&relid, subrel_local_oids,
1200 tbl_count, sizeof(Oid), oid_cmp) &&
1201 !bsearch(&relid, subseq_local_oids,
1202 seq_count, sizeof(Oid), oid_cmp))
1203 {
1204 AddSubscriptionRelState(sub->oid, relid,
1208 errmsg_internal("%s \"%s.%s\" added to subscription \"%s\"",
1209 relkind == RELKIND_SEQUENCE ? "sequence" : "table",
1210 rv->schemaname, rv->relname, sub->name));
1211 }
1212 }
1213
1214 /*
1215 * Next remove state for tables we should not care about anymore using
1216 * the data we collected above
1217 */
1219
1220 for (off = 0; off < tbl_count; off++)
1221 {
1222 Oid relid = subrel_local_oids[off];
1223
1224 if (!bsearch(&relid, pubrel_local_oids,
1225 list_length(pubrels), sizeof(Oid), oid_cmp))
1226 {
1227 char state;
1228 XLogRecPtr statelsn;
1230
1231 /*
1232 * Lock pg_subscription_rel with AccessExclusiveLock to
1233 * prevent any race conditions with the apply worker
1234 * re-launching workers at the same time this code is trying
1235 * to remove those tables.
1236 *
1237 * Even if new worker for this particular rel is restarted it
1238 * won't be able to make any progress as we hold exclusive
1239 * lock on pg_subscription_rel till the transaction end. It
1240 * will simply exit as there is no corresponding rel entry.
1241 *
1242 * This locking also ensures that the state of rels won't
1243 * change till we are done with this refresh operation.
1244 */
1245 if (!rel)
1247
1248 /* Last known rel state. */
1249 state = GetSubscriptionRelState(sub->oid, relid, &statelsn);
1250
1251 RemoveSubscriptionRel(sub->oid, relid);
1252
1253 remove_rel->relid = relid;
1254 remove_rel->state = state;
1255
1257
1259
1260 /*
1261 * For READY state, we would have already dropped the
1262 * tablesync origin.
1263 */
1265 {
1266 char originname[NAMEDATALEN];
1267
1268 /*
1269 * Drop the tablesync's origin tracking if exists.
1270 *
1271 * It is possible that the origin is not yet created for
1272 * tablesync worker, this can happen for the states before
1273 * SUBREL_STATE_DATASYNC. The tablesync worker or apply
1274 * worker can also concurrently try to drop the origin and
1275 * by this time the origin might be already removed. For
1276 * these reasons, passing missing_ok = true.
1277 */
1279 sizeof(originname));
1280 replorigin_drop_by_name(originname, true, false);
1281 }
1282
1284 (errmsg_internal("table \"%s.%s\" removed from subscription \"%s\"",
1286 get_rel_name(relid),
1287 sub->name)));
1288 }
1289 }
1290
1291 /*
1292 * Drop the tablesync slots associated with removed tables. This has
1293 * to be at the end because otherwise if there is an error while doing
1294 * the database operations we won't be able to rollback dropped slots.
1295 */
1297 {
1298 if (sub_remove_rel->state != SUBREL_STATE_READY &&
1300 {
1301 char syncslotname[NAMEDATALEN] = {0};
1302
1303 /*
1304 * For READY/SYNCDONE states we know the tablesync slot has
1305 * already been dropped by the tablesync worker.
1306 *
1307 * For other states, there is no certainty, maybe the slot
1308 * does not exist yet. Also, if we fail after removing some of
1309 * the slots, next time, it will again try to drop already
1310 * dropped slots and fail. For these reasons, we allow
1311 * missing_ok = true for the drop.
1312 */
1314 syncslotname, sizeof(syncslotname));
1316 }
1317 }
1318
1319 /*
1320 * Next remove state for sequences we should not care about anymore
1321 * using the data we collected above
1322 */
1323 for (off = 0; off < seq_count; off++)
1324 {
1325 Oid relid = subseq_local_oids[off];
1326
1327 if (!bsearch(&relid, pubrel_local_oids,
1328 list_length(pubrels), sizeof(Oid), oid_cmp))
1329 {
1330 /*
1331 * This locking ensures that the state of rels won't change
1332 * till we are done with this refresh operation.
1333 */
1334 if (!rel)
1336
1337 RemoveSubscriptionRel(sub->oid, relid);
1338
1340 errmsg_internal("sequence \"%s.%s\" removed from subscription \"%s\"",
1342 get_rel_name(relid),
1343 sub->name));
1344 }
1345 }
1346 }
1347 PG_FINALLY();
1348 {
1350 }
1351 PG_END_TRY();
1352
1353 if (rel)
1354 table_close(rel, NoLock);
1355}
int int errmsg_internal(const char *fmt,...) pg_attribute_printf(1
#define DEBUG1
Definition elog.h:31
void CheckSubscriptionRelkind(char localrelkind, char remoterelkind, const char *nspname, const char *relname)
#define palloc_object(type)
Definition fe_memutils.h:89
void logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
Definition launcher.c:662
List * lappend(List *list, void *datum)
Definition list.c:339
#define NoLock
Definition lockdefs.h:34
#define AccessShareLock
Definition lockdefs.h:36
char * get_rel_name(Oid relid)
Definition lsyscache.c:2242
char get_rel_relkind(Oid relid)
Definition lsyscache.c:2317
Oid get_rel_namespace(Oid relid)
Definition lsyscache.c:2266
char * get_namespace_name(Oid nspid)
Definition lsyscache.c:3682
void * palloc(Size size)
Definition mcxt.c:1390
#define RangeVarGetRelid(relation, lockmode, missing_ok)
Definition namespace.h:98
int oid_cmp(const void *p1, const void *p2)
Definition oid.c:287
void replorigin_drop_by_name(const char *name, bool missing_ok, bool nowait)
Definition origin.c:459
NameData relname
Definition pg_class.h:40
#define lfirst(lc)
Definition pg_list.h:172
static int list_length(const List *l)
Definition pg_list.h:152
#define NIL
Definition pg_list.h:68
#define foreach_ptr(type, var, lst)
Definition pg_list.h:501
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)
List * GetSubscriptionRelations(Oid subid, bool tables, bool sequences, bool not_ready)
#define qsort(a, b, c, d)
Definition port.h:496
char * relname
Definition primnodes.h:84
char * schemaname
Definition primnodes.h:81
static void check_publications_origin_sequences(WalReceiverConn *wrconn, List *publications, bool copydata, char *origin, Oid *subrel_local_oids, int subrel_count, char *subname)
static void check_publications(WalReceiverConn *wrconn, List *publications)
static List * fetch_relation_list(WalReceiverConn *wrconn, List *publications)
void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok)
void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot)
Definition tablesync.c:1236
@ WORKERTYPE_TABLESYNC
#define InvalidXLogRecPtr
Definition xlogdefs.h:28

References AccessExclusiveLock, AccessShareLock, AddSubscriptionRelState(), check_publications(), check_publications_origin_sequences(), check_publications_origin_tables(), CheckSubscriptionRelkind(), Subscription::conninfo, DEBUG1, ereport, err(), errcode(), errmsg, errmsg_internal(), ERROR, fb(), fetch_relation_list(), foreach_ptr, get_namespace_name(), get_rel_name(), get_rel_namespace(), get_rel_relkind(), GetSubscriptionRelations(), GetSubscriptionRelState(), InvalidXLogRecPtr, lappend(), lfirst, list_length(), load_file(), logicalrep_worker_stop(), Subscription::name, NAMEDATALEN, NIL, NoLock, Subscription::oid, oid_cmp(), Subscription::origin, Subscription::ownersuperuser, palloc(), palloc_object, 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, WORKERTYPE_TABLESYNC, and wrconn.

Referenced by AlterSubscription().

◆ AlterSubscription_refresh_seq()

static void AlterSubscription_refresh_seq ( Subscription sub)
static

Definition at line 1361 of file subscriptioncmds.c.

1362{
1363 char *err = NULL;
1365 bool must_use_password;
1367
1368 /* Load the library providing us libpq calls. */
1369 load_file("libpqwalreceiver", false);
1370
1371 /* Try to connect to the publisher. */
1373 wrconn = walrcv_connect(sub->conninfo, true, true, must_use_password,
1374 sub->name, &err);
1375 if (!wrconn)
1376 ereport(ERROR,
1378 errmsg("subscription \"%s\" could not connect to the publisher: %s",
1379 sub->name, err));
1380
1381 /* The publisher connection is only needed for the origin check. */
1382 PG_TRY();
1383 {
1385 sub->origin, NULL, 0, sub->name);
1386 }
1387 PG_FINALLY();
1388 {
1390 }
1391 PG_END_TRY();
1392
1393 /*
1394 * Reset the sequences to INIT so they get re-synchronized with the latest
1395 * publisher values.
1396 *
1397 * A sequence sync worker may already be running. If it has fetched a
1398 * sequence's value from the publisher but not yet marked it READY, it
1399 * must not be allowed to complete that update, as it would overwrite the
1400 * reset below with a stale value and silently lose this refresh request.
1401 * So we stop any running sequence sync worker before resetting the
1402 * states.
1403 *
1404 * This is race-free because AlterSubscription() already holds
1405 * AccessExclusiveLock on the subscription object. That lock blocks a
1406 * running worker's update of sequence state to READY, see
1407 * UpdateSubscriptionRelState() which takes AccessShareLock on the object.
1408 * It also blocks any worker the apply worker re-launches, because a new
1409 * worker takes AccessShareLock on the object before it reads
1410 * pg_subscription_rel, see InitializeLogRepWorker(). Such a worker cannot
1411 * act on the states until we commit, by which time they are reset to INIT
1412 * and it will sync the latest values.
1413 */
1414#ifdef USE_ASSERT_CHECKING
1415 {
1416 LOCKTAG tag;
1417
1420 }
1421#endif
1422
1424
1425 /* Reset every local sequence of this subscription to INIT. */
1426 subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
1428 {
1429 Oid relid = subrel->relid;
1430
1432 InvalidXLogRecPtr, false);
1434 errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
1436 get_rel_name(relid),
1437 sub->name));
1438 }
1439}
bool LockHeldByMe(const LOCKTAG *locktag, LOCKMODE lockmode, bool orstronger)
Definition lock.c:640
#define SET_LOCKTAG_OBJECT(locktag, dboid, classoid, objoid, objsubid)
Definition locktag.h:162
void UpdateSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn, bool already_locked)
@ WORKERTYPE_SEQUENCESYNC

References AccessExclusiveLock, Assert, check_publications_origin_sequences(), Subscription::conninfo, DEBUG1, ereport, err(), errcode(), errmsg, errmsg_internal(), ERROR, fb(), foreach_ptr, get_namespace_name(), get_rel_name(), get_rel_namespace(), GetSubscriptionRelations(), InvalidOid, InvalidXLogRecPtr, load_file(), LockHeldByMe(), logicalrep_worker_stop(), Subscription::name, Subscription::oid, Subscription::origin, Subscription::ownersuperuser, Subscription::passwordrequired, PG_END_TRY, PG_FINALLY, PG_TRY, Subscription::publications, SET_LOCKTAG_OBJECT, UpdateSubscriptionRelState(), walrcv_connect, walrcv_disconnect, WORKERTYPE_SEQUENCESYNC, and wrconn.

Referenced by AlterSubscription().

◆ AlterSubscriptionOwner()

ObjectAddress AlterSubscriptionOwner ( const char name,
Oid  newOwnerId 
)

Definition at line 2986 of file subscriptioncmds.c.

2987{
2988 Oid subid;
2989 HeapTuple tup;
2990 Relation rel;
2991 ObjectAddress address;
2993
2995
2998
2999 if (!HeapTupleIsValid(tup))
3000 ereport(ERROR,
3002 errmsg("subscription \"%s\" does not exist", name)));
3003
3005 subid = form->oid;
3006
3008
3010
3012
3014
3015 return address;
3016}
static void AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)

References AlterSubscriptionOwner_internal(), CStringGetDatum(), ereport, errcode(), errmsg, ERROR, fb(), Form_pg_subscription, 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 2897 of file subscriptioncmds.c.

2898{
2901
2903
2904 /* Must only alter subscriptions belonging to the current database. */
2905 Assert(form->subdbid == MyDatabaseId);
2906
2907 if (form->subowner == newOwnerId)
2908 return;
2909
2912 NameStr(form->subname));
2913
2914 /*
2915 * Don't allow non-superuser modification of a subscription with
2916 * password_required=false.
2917 */
2918 if (!form->subpasswordrequired && !superuser())
2919 ereport(ERROR,
2921 errmsg("password_required=false is superuser-only"),
2922 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
2923
2924 /* Must be able to become new owner */
2926
2927 /*
2928 * current owner must have CREATE on database
2929 *
2930 * This is consistent with how ALTER SCHEMA ... OWNER TO works, but some
2931 * other object types behave differently (e.g. you can't give a table to a
2932 * user who lacks CREATE privileges on a schema).
2933 */
2936 if (aclresult != ACLCHECK_OK)
2939
2940 /*
2941 * If the subscription uses a server, check that the new owner has USAGE
2942 * privileges on the server and that a user mapping exists. Note: does not
2943 * re-check the resulting connection string.
2944 */
2945 if (OidIsValid(form->subserver))
2946 {
2947 ForeignServer *server = GetForeignServer(form->subserver);
2948
2950 if (aclresult != ACLCHECK_OK)
2951 ereport(ERROR,
2953 errmsg("new subscription owner \"%s\" does not have permission on foreign server \"%s\"",
2955 server->servername));
2956
2957 /* make sure a user mapping exists */
2959 }
2960
2961 form->subowner = newOwnerId;
2962 CatalogTupleUpdate(rel, &tup->t_self, tup);
2963
2964 /* Update owner of the conflict log table if it exists. */
2965 if (OidIsValid(form->subconflictlogrelid))
2966 ATExecChangeOwner(form->subconflictlogrelid, newOwnerId, true,
2968
2969 /* Update owner dependency reference */
2971 form->oid,
2972 newOwnerId);
2973
2975 form->oid, 0);
2976
2977 /* Wake up related background processes to handle this change quickly. */
2980}
void check_can_set_role(Oid member, Oid role)
Definition acl.c:5374
#define NameStr(name)
Definition c.h:894
#define OidIsValid(objectId)
Definition c.h:917
ForeignServer * GetForeignServer(Oid serverid)
Definition foreign.c:114
char * get_database_name(Oid dbid)
Definition lsyscache.c:1392
@ OBJECT_DATABASE
#define ACL_CREATE
Definition parsenodes.h:85
void changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
char * servername
Definition foreign.h:40
void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)

References AccessExclusiveLock, ACL_CREATE, ACL_USAGE, aclcheck_error(), ACLCHECK_NOT_OWNER, ACLCHECK_OK, ApplyLauncherWakeupAtCommit(), Assert, ATExecChangeOwner(), CatalogTupleUpdate(), changeDependencyOnOwner(), check_can_set_role(), ereport, errcode(), errhint(), errmsg, ERROR, fb(), Form_pg_subscription, get_database_name(), GetForeignServer(), GETSTRUCT(), GetUserId(), GetUserMapping(), GetUserNameFromId(), InvokeObjectPostAlterHook, LogicalRepWorkersWakeupAtCommit(), MyDatabaseId, NameStr, object_aclcheck(), OBJECT_DATABASE, object_ownercheck(), OBJECT_SUBSCRIPTION, OidIsValid, ForeignServer::serverid, ForeignServer::servername, and superuser().

Referenced by AlterSubscriptionOwner(), and AlterSubscriptionOwner_oid().

◆ AlterSubscriptionOwner_oid()

void AlterSubscriptionOwner_oid ( Oid  subid,
Oid  newOwnerId 
)

Definition at line 3022 of file subscriptioncmds.c.

3023{
3024 HeapTuple tup;
3025 Relation rel;
3027
3029
3031
3032 if (!HeapTupleIsValid(tup))
3033 ereport(ERROR,
3035 errmsg("subscription with OID %u does not exist", subid)));
3036
3038
3039 /*
3040 * Don't process subscriptions belonging to other databases. While
3041 * pg_subscription is a shared catalog, subscriptions refer to db-local
3042 * objects which exist only in the database identified by subdbid.
3043 */
3044 if (form->subdbid == MyDatabaseId)
3046
3048
3050}
#define SearchSysCacheCopy1(cacheId, key1)
Definition syscache.h:91

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

Referenced by shdepReassignOwned_Owner().

◆ appendQuotedString()

static void appendQuotedString ( StringInfo  buf,
const char str,
char  quote 
)
static

Definition at line 554 of file subscriptioncmds.c.

555{
557 while (*str)
558 {
559 char c = *str++;
560
561 if (c == quote)
564 }
566}
const char * str
static char buf[DEFAULT_XLOG_SEG_SIZE]
char * c
void appendStringInfoChar(StringInfo str, char ch)
Definition stringinfo.c:242

References appendStringInfoChar(), buf, and str.

◆ check_duplicates_in_publist()

static void check_duplicates_in_publist ( List publist,
Datum datums 
)
static

Definition at line 3634 of file subscriptioncmds.c.

3635{
3636 ListCell *cell;
3637 int j = 0;
3638
3639 foreach(cell, publist)
3640 {
3641 char *name = strVal(lfirst(cell));
3642 ListCell *pcell;
3643
3644 foreach(pcell, publist)
3645 {
3646 char *pname = strVal(lfirst(pcell));
3647
3648 if (pcell == cell)
3649 break;
3650
3651 if (strcmp(name, pname) == 0)
3652 ereport(ERROR,
3654 errmsg("publication name \"%s\" used more than once",
3655 pname)));
3656 }
3657
3658 if (datums)
3659 datums[j++] = CStringGetTextDatum(name);
3660 }
3661}
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, fb(), j, lfirst, name, and strVal.

Referenced by merge_publications().

◆ check_pub_dead_tuple_retention()

static void check_pub_dead_tuple_retention ( WalReceiverConn wrconn)
static

Definition at line 3341 of file subscriptioncmds.c.

3342{
3343 WalRcvExecResult *res;
3344 Oid RecoveryRow[1] = {BOOLOID};
3345 TupleTableSlot *slot;
3346 bool isnull;
3347 bool remote_in_recovery;
3348
3349 if (walrcv_server_version(wrconn) < 190000)
3350 ereport(ERROR,
3352 errmsg("cannot enable retain_dead_tuples if the publisher is running a version earlier than PostgreSQL 19"));
3353
3354 res = walrcv_exec(wrconn, "SELECT pg_is_in_recovery()", 1, RecoveryRow);
3355
3356 if (res->status != WALRCV_OK_TUPLES)
3357 ereport(ERROR,
3359 errmsg("could not obtain recovery progress from the publisher: %s",
3360 res->err)));
3361
3363 if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3364 elog(ERROR, "failed to fetch tuple for the recovery progress");
3365
3366 remote_in_recovery = DatumGetBool(slot_getattr(slot, 1, &isnull));
3367
3369 ereport(ERROR,
3371 errmsg("cannot enable retain_dead_tuples if the publisher is in recovery"));
3372
3374
3376}
TupleTableSlot * MakeSingleTupleTableSlot(TupleDesc tupdesc, const TupleTableSlotOps *tts_ops)
void ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
const TupleTableSlotOps TTSOpsMinimalTuple
Definition execTuples.c:86
static bool DatumGetBool(Datum X)
Definition postgres.h:100
Tuplestorestate * tuplestore
TupleDesc tupledesc
WalRcvExecStatus status
bool tuplestore_gettupleslot(Tuplestorestate *state, bool forward, bool copy, TupleTableSlot *slot)
static Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
Definition tuptable.h:417
@ WALRCV_OK_TUPLES
static void walrcv_clear_result(WalRcvExecResult *walres)
#define walrcv_server_version(conn)
#define walrcv_exec(conn, exec, nRetTypes, retTypes)

References DatumGetBool(), elog, ereport, WalRcvExecResult::err, errcode(), errmsg, ERROR, ExecDropSingleTupleTableSlot(), fb(), 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().

◆ check_publications()

static void check_publications ( WalReceiverConn wrconn,
List publications 
)
static

Definition at line 575 of file subscriptioncmds.c.

576{
577 WalRcvExecResult *res;
578 StringInfoData cmd;
579 TupleTableSlot *slot;
581 Oid tableRow[1] = {TEXTOID};
582
583 initStringInfo(&cmd);
584 appendStringInfoString(&cmd, "SELECT t.pubname FROM\n"
585 " pg_catalog.pg_publication t WHERE\n"
586 " t.pubname IN (");
587 GetPublicationsStr(publications, &cmd, true);
588 appendStringInfoChar(&cmd, ')');
589
590 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
591 pfree(cmd.data);
592
593 if (res->status != WALRCV_OK_TUPLES)
595 errmsg("could not receive list of publications from the publisher: %s",
596 res->err));
597
598 publicationsCopy = list_copy(publications);
599
600 /* Process publication(s). */
602 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
603 {
604 char *pubname;
605 bool isnull;
606
607 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
608 Assert(!isnull);
609
610 /* Delete the publication present in publisher from the list. */
612 ExecClearTuple(slot);
613 }
614
616
618
620 {
621 /* Prepare the list of non-existent publication(s) for error message. */
623
625
629 errmsg_plural("publication %s does not exist on the publisher",
630 "publications %s do not exist on the publisher",
632 pubnames.data));
633 }
634}
#define TextDatumGetCString(d)
Definition builtins.h:99
int int int errmsg_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...) pg_attribute_printf(1
List * list_delete(List *list, void *datum)
Definition list.c:853
List * list_copy(const List *oldlist)
Definition list.c:1573
void pfree(void *pointer)
Definition mcxt.c:1619
void GetPublicationsStr(List *publications, StringInfo dest, bool quote_literal)
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void initStringInfo(StringInfo str)
Definition stringinfo.c:97
static TupleTableSlot * ExecClearTuple(TupleTableSlot *slot)
Definition tuptable.h:476
String * makeString(char *str)
Definition value.c:63

Referenced by AlterSubscription_refresh().

◆ check_publications_origin_sequences()

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

Definition at line 3224 of file subscriptioncmds.c.

3228{
3229 WalRcvExecResult *res;
3230 StringInfoData cmd;
3231 TupleTableSlot *slot;
3232 Oid tableRow[1] = {TEXTOID};
3233 List *publist = NIL;
3234
3235 /*
3236 * Enable sequence synchronization checks only when origin is 'none' , to
3237 * ensure that sequence data from other origins is not inadvertently
3238 * copied. This check is necessary if the publisher is running PG19 or
3239 * later, where logical replication sequence synchronization is supported.
3240 */
3241 if (!copydata || pg_strcasecmp(origin, LOGICALREP_ORIGIN_NONE) != 0 ||
3242 walrcv_server_version(wrconn) < 190000)
3243 return;
3244
3245 initStringInfo(&cmd);
3247 "SELECT DISTINCT P.pubname AS pubname\n"
3248 "FROM pg_publication P,\n"
3249 " LATERAL pg_get_publication_sequences(P.pubname) GPS\n"
3250 " JOIN pg_subscription_rel PS ON (GPS.relid = PS.srrelid),\n"
3251 " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
3252 "WHERE C.oid = GPS.relid AND P.pubname IN (");
3253
3254 GetPublicationsStr(publications, &cmd, true);
3255 appendStringInfoString(&cmd, ")\n");
3256
3257 /*
3258 * In case of ALTER SUBSCRIPTION ... REFRESH PUBLICATION,
3259 * subrel_local_oids contains the list of relations that are already
3260 * present on the subscriber. This check should be skipped as these will
3261 * not be re-synced.
3262 */
3263 for (int i = 0; i < subrel_count; i++)
3264 {
3265 Oid relid = subrel_local_oids[i];
3266 char *schemaname = get_namespace_name(get_rel_namespace(relid));
3267 char *seqname = get_rel_name(relid);
3268 char *schemaname_lit = quote_literal_cstr(schemaname);
3269 char *seqname_lit = quote_literal_cstr(seqname);
3270
3271 appendStringInfo(&cmd,
3272 "AND NOT (N.nspname = %s AND C.relname = %s)\n",
3274
3277 }
3278
3279 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
3280 pfree(cmd.data);
3281
3282 if (res->status != WALRCV_OK_TUPLES)
3283 ereport(ERROR,
3285 errmsg("could not receive list of replicated sequences from the publisher: %s",
3286 res->err)));
3287
3288 /* Process publications. */
3290 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3291 {
3292 char *pubname;
3293 bool isnull;
3294
3295 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3296 Assert(!isnull);
3297
3298 ExecClearTuple(slot);
3300 }
3301
3302 /*
3303 * Log a warning if the publisher has subscribed to the same sequence from
3304 * some other publisher. We cannot know the origin of sequences data
3305 * during the initial sync.
3306 */
3307 if (publist)
3308 {
3310
3311 /* Prepare the list of publication(s) for warning message. */
3314
3317 errmsg("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin",
3318 subname),
3319 errdetail_plural("The subscription subscribes to a publication (%s) that contains sequences that are written to by other subscriptions.",
3320 "The subscription subscribes to publications (%s) that contain sequences that are written to by other subscriptions.",
3322 errhint("Verify that initial data copied from the publisher sequences did not come from other origins."));
3323 }
3324
3326
3328}
int errdetail_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...) pg_attribute_printf(1
int i
Definition isn.c:77
List * list_append_unique(List *list, void *datum)
Definition list.c:1343
NameData subname
char * quote_literal_cstr(const char *rawstr)
Definition quote.c:101
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145

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

Referenced by AlterSubscription_refresh(), and AlterSubscription_refresh_seq().

◆ check_publications_origin_tables()

static void check_publications_origin_tables ( 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 3076 of file subscriptioncmds.c.

3080{
3081 WalRcvExecResult *res;
3082 StringInfoData cmd;
3083 TupleTableSlot *slot;
3084 Oid tableRow[1] = {TEXTOID};
3085 List *publist = NIL;
3086 int i;
3087 bool check_rdt;
3088 bool check_table_sync;
3089 bool origin_none = origin &&
3091
3092 /*
3093 * Enable retain_dead_tuples checks only when origin is set to 'any',
3094 * since with origin='none' only local changes are replicated to the
3095 * subscriber.
3096 */
3098
3099 /*
3100 * Enable table synchronization checks only when origin is 'none', to
3101 * ensure that data from other origins is not inadvertently copied.
3102 */
3104
3105 /* retain_dead_tuples and table sync checks occur separately */
3107
3108 /* Return if no checks are required */
3109 if (!check_rdt && !check_table_sync)
3110 return;
3111
3112 initStringInfo(&cmd);
3114 "SELECT DISTINCT P.pubname AS pubname\n"
3115 "FROM pg_publication P,\n"
3116 " LATERAL pg_get_publication_tables(P.pubname) GPT\n"
3117 " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR"
3118 " GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION"
3119 " SELECT relid FROM pg_partition_tree(PS.srrelid))),\n"
3120 " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
3121 "WHERE C.oid = GPT.relid AND P.pubname IN (");
3122 GetPublicationsStr(publications, &cmd, true);
3123 appendStringInfoString(&cmd, ")\n");
3124
3125 /*
3126 * In case of ALTER SUBSCRIPTION ... REFRESH PUBLICATION,
3127 * subrel_local_oids contains the list of relation oids that are already
3128 * present on the subscriber. This check should be skipped for these
3129 * tables if checking for table sync scenario. However, when handling the
3130 * retain_dead_tuples scenario, ensure all tables are checked, as some
3131 * existing tables may now include changes from other origins due to newly
3132 * created subscriptions on the publisher.
3133 */
3134 if (check_table_sync)
3135 {
3136 for (i = 0; i < subrel_count; i++)
3137 {
3138 Oid relid = subrel_local_oids[i];
3139 char *schemaname = get_namespace_name(get_rel_namespace(relid));
3140 char *tablename = get_rel_name(relid);
3141 char *schemaname_lit = quote_literal_cstr(schemaname);
3142 char *tablename_lit = quote_literal_cstr(tablename);
3143
3144 appendStringInfo(&cmd, "AND NOT (N.nspname = %s AND C.relname = %s)\n",
3146
3149 }
3150 }
3151
3152 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
3153 pfree(cmd.data);
3154
3155 if (res->status != WALRCV_OK_TUPLES)
3156 ereport(ERROR,
3158 errmsg("could not receive list of replicated tables from the publisher: %s",
3159 res->err)));
3160
3161 /* Process publications. */
3163 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3164 {
3165 char *pubname;
3166 bool isnull;
3167
3168 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3169 Assert(!isnull);
3170
3171 ExecClearTuple(slot);
3173 }
3174
3175 /*
3176 * Log a warning if the publisher has subscribed to the same table from
3177 * some other publisher. We cannot know the origin of data during the
3178 * initial sync. Data origins can be found only from the WAL by looking at
3179 * the origin id.
3180 *
3181 * XXX: For simplicity, we don't check whether the table has any data or
3182 * not. If the table doesn't have any data then we don't need to
3183 * distinguish between data having origin and data not having origin so we
3184 * can avoid logging a warning for table sync scenario.
3185 */
3186 if (publist)
3187 {
3189
3190 /* Prepare the list of publication(s) for warning message. */
3193
3194 if (check_table_sync)
3197 errmsg("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin",
3198 subname),
3199 errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
3200 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
3202 errhint("Verify that initial data copied from the publisher tables did not come from other origins."));
3203 else
3206 errmsg("subscription \"%s\" enabled retain_dead_tuples but might not reliably detect conflicts for changes from different origins",
3207 subname),
3208 errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
3209 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
3211 errhint("Consider using origin = NONE or disabling retain_dead_tuples."));
3212 }
3213
3215
3217}

References appendStringInfo(), appendStringInfoString(), Assert, StringInfoData::data, ereport, WalRcvExecResult::err, errcode(), errdetail_plural(), errhint(), errmsg, ERROR, ExecClearTuple(), ExecDropSingleTupleTableSlot(), fb(), get_namespace_name(), get_rel_name(), get_rel_namespace(), GetPublicationsStr(), i, initStringInfo(), list_append_unique(), list_length(), MakeSingleTupleTableSlot(), makeString(), NIL, pfree(), pg_strcasecmp(), quote_literal_cstr(), 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(), and AlterSubscription_refresh().

◆ CheckAlterSubOption()

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

Definition at line 1446 of file subscriptioncmds.c.

1448{
1449 Assert(strcmp(option, "failover") == 0 ||
1450 strcmp(option, "two_phase") == 0 ||
1451 strcmp(option, "retain_dead_tuples") == 0);
1452
1453 /*
1454 * Altering the retain_dead_tuples option does not update the slot on the
1455 * publisher.
1456 */
1457 Assert(!slot_needs_update || strcmp(option, "retain_dead_tuples") != 0);
1458
1459 /*
1460 * Do not allow changing the option if the subscription is enabled. This
1461 * is because both failover and two_phase options of the slot on the
1462 * publisher cannot be modified if the slot is currently acquired by the
1463 * existing walsender.
1464 *
1465 * Note that two_phase is enabled (aka changed from 'false' to 'true') on
1466 * the publisher by the existing walsender, so we could have allowed that
1467 * even when the subscription is enabled. But we kept this restriction for
1468 * the sake of consistency and simplicity.
1469 *
1470 * Additionally, do not allow changing the retain_dead_tuples option when
1471 * the subscription is enabled to prevent race conditions arising from the
1472 * new option value being acknowledged asynchronously by the launcher and
1473 * apply workers.
1474 *
1475 * Without the restriction, a race condition may arise when a user
1476 * disables and immediately re-enables the retain_dead_tuples option. In
1477 * this case, the launcher might drop the slot upon noticing the disabled
1478 * action, while the apply worker may keep maintaining
1479 * oldest_nonremovable_xid without noticing the option change. During this
1480 * period, a transaction ID wraparound could falsely make this ID appear
1481 * as if it originates from the future w.r.t the transaction ID stored in
1482 * the slot maintained by launcher.
1483 *
1484 * Similarly, if the user enables retain_dead_tuples concurrently with the
1485 * launcher starting the worker, the apply worker may start calculating
1486 * oldest_nonremovable_xid before the launcher notices the enable action.
1487 * Consequently, the launcher may update slot.xmin to a newer value than
1488 * that maintained by the worker. In subsequent cycles, upon integrating
1489 * the worker's oldest_nonremovable_xid, the launcher might detect a
1490 * retreat in the calculated xmin, necessitating additional handling.
1491 *
1492 * XXX To address the above race conditions, we can define
1493 * oldest_nonremovable_xid as FullTransactionId and adds the check to
1494 * disallow retreating the conflict slot's xmin. For now, we kept the
1495 * implementation simple by disallowing change to the retain_dead_tuples,
1496 * but in the future we can change this after some more analysis.
1497 *
1498 * Note that we could restrict only the enabling of retain_dead_tuples to
1499 * avoid the race conditions described above, but we maintain the
1500 * restriction for both enable and disable operations for the sake of
1501 * consistency.
1502 */
1503 if (sub->enabled)
1504 ereport(ERROR,
1506 errmsg("cannot set option \"%s\" for enabled subscription",
1507 option)));
1508
1510 {
1511 StringInfoData cmd;
1512
1513 /*
1514 * A valid slot must be associated with the subscription for us to
1515 * modify any of the slot's properties.
1516 */
1517 if (!sub->slotname)
1518 ereport(ERROR,
1520 errmsg("cannot set option \"%s\" for a subscription that does not have a slot name",
1521 option)));
1522
1523 /* The changed option of the slot can't be rolled back. */
1524 initStringInfo(&cmd);
1525 appendStringInfo(&cmd, "ALTER SUBSCRIPTION ... SET (%s)", option);
1526
1528 pfree(cmd.data);
1529 }
1530}

References appendStringInfo(), Assert, StringInfoData::data, Subscription::enabled, ereport, errcode(), errmsg, ERROR, fb(), 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 3402 of file subscriptioncmds.c.

3406{
3409
3411 {
3413 ereport(ERROR,
3415 errmsg("\"wal_level\" is insufficient to create the replication slot required by retain_dead_tuples"),
3416 errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start."));
3417
3421 errmsg("commit timestamp and origin data required for detecting conflicts won't be retained"),
3422 errhint("Consider setting \"%s\" to true.",
3423 "track_commit_timestamp"));
3424
3428 errmsg("deleted rows to detect conflicts would not be removed until the subscription is enabled"),
3430 ? errhint("Consider setting %s to false.",
3431 "retain_dead_tuples") : 0);
3432 }
3433 else if (max_retention_set)
3434 {
3437 errmsg("max_retention_duration is ineffective when retain_dead_tuples is disabled"));
3438 }
3439}
bool track_commit_timestamp
Definition commit_ts.c:121
int wal_level
Definition xlog.c:138
@ WAL_LEVEL_REPLICA
Definition xlog.h:77

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

Referenced by AlterSubscription(), and DisableSubscriptionAndExit().

◆ construct_subserver_conninfo()

static char * construct_subserver_conninfo ( Oid  subserver,
Oid  subowner,
char **  err 
)
static

Definition at line 2458 of file subscriptioncmds.c.

2459{
2461 ForeignServer *server;
2462
2463 *err = NULL;
2464
2465 server = GetForeignServer(subserver);
2466
2469 if (aclresult != ACLCHECK_OK)
2470 {
2471 /*
2472 * Unable to generate connection string because permissions on the
2473 * foreign server have been removed. Follow the same logic as an
2474 * unusable subconninfo (which will result in an ERROR later unless
2475 * slot_name = NONE).
2476 */
2477 *err = psprintf(_("subscription owner \"%s\" does not have permission on foreign server \"%s\""),
2479 server->servername);
2480 return NULL;
2481 }
2482
2484}
#define _(x)
Definition elog.c:96
char * psprintf(const char *fmt,...)
Definition psprintf.c:43

References _, ACL_USAGE, ACLCHECK_OK, err(), fb(), ForeignServerConnectionString(), GetForeignServer(), GetUserNameFromId(), object_aclcheck(), psprintf(), and ForeignServer::servername.

Referenced by DropSubscription().

◆ CreateSubscription()

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

Definition at line 670 of file subscriptioncmds.c.

672{
673 Relation rel;
675 Oid subid;
676 bool nulls[Natts_pg_subscription];
678 Oid owner = GetUserId();
680 Oid serverid;
681 char *conninfo;
683 List *publications;
685 SubOpts opts = {0};
688
689 /*
690 * Parse and check options.
691 *
692 * Connection and publication should not be specified here.
693 */
705
706 /*
707 * Since creating a replication slot is not transactional, rolling back
708 * the transaction leaves the created replication slot. So we cannot run
709 * CREATE SUBSCRIPTION inside a transaction block if creating a
710 * replication slot.
711 */
712 if (opts.create_slot)
713 PreventInTransactionBlock(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
714
715 /*
716 * We don't want to allow unprivileged users to be able to trigger
717 * attempts to access arbitrary network destinations, so require the user
718 * to have been specifically authorized to create subscriptions.
719 */
723 errmsg("permission denied to create subscription"),
724 errdetail("Only roles with privileges of the \"%s\" role may create subscriptions.",
725 "pg_create_subscription")));
726
727 /*
728 * Since a subscription is a database object, we also check for CREATE
729 * permission on the database.
730 */
732 owner, ACL_CREATE);
733 if (aclresult != ACLCHECK_OK)
736
737 /*
738 * Non-superusers are required to set a password for authentication, and
739 * that password must be used by the target server, but the superuser can
740 * exempt a subscription from this requirement.
741 */
742 if (!opts.passwordrequired && !superuser_arg(owner))
745 errmsg("password_required=false is superuser-only"),
746 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
747
748 /*
749 * If built with appropriate switch, whine when regression-testing
750 * conventions for subscription names are violated.
751 */
752#ifdef ENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS
753 if (strncmp(stmt->subname, "regress_", 8) != 0)
754 elog(WARNING, "subscriptions created by regression test cases should have names starting with \"regress_\"");
755#endif
756
758
759 /* Check if name is used */
762 if (OidIsValid(subid))
763 {
766 errmsg("subscription \"%s\" already exists",
767 stmt->subname)));
768 }
769
770 /*
771 * Ensure that system configuration parameters are set appropriately to
772 * support retain_dead_tuples and max_retention_duration.
773 */
775 opts.retaindeadtuples, opts.retaindeadtuples,
776 (opts.maxretention > 0));
777
778 if (!IsSet(opts.specified_opts, SUBOPT_SLOT_NAME) &&
779 opts.slot_name == NULL)
780 opts.slot_name = stmt->subname;
781
782 /* The default for synchronous_commit of subscriptions is off. */
783 if (opts.synchronous_commit == NULL)
784 opts.synchronous_commit = "off";
785
786 /*
787 * The default for wal_receiver_timeout of subscriptions is -1, which
788 * means the value is inherited from the server configuration, command
789 * line, or role/database settings.
790 */
791 if (opts.wal_receiver_timeout == NULL)
792 opts.wal_receiver_timeout = "-1";
793
794 /* Load the library providing us libpq calls. */
795 load_file("libpqwalreceiver", false);
796
797 if (stmt->servername)
798 {
799 ForeignServer *server;
800
801 Assert(!stmt->conninfo);
802 conninfo = NULL;
803
804 server = GetForeignServerByName(stmt->servername, false);
806 if (aclresult != ACLCHECK_OK)
808
809 /* make sure a user mapping exists */
810 GetUserMapping(owner, server->serverid);
811
812 serverid = server->serverid;
813 conninfo = ForeignServerConnectionString(owner, server);
814 }
815 else
816 {
817 Assert(stmt->conninfo);
818
819 serverid = InvalidOid;
820 conninfo = stmt->conninfo;
821 }
822
823 /* Check the connection info string. */
824 walrcv_check_conninfo(conninfo, opts.passwordrequired && !superuser());
825
826 publications = stmt->publication;
827
828 /* Everything ok, form a new tuple. */
829 memset(values, 0, sizeof(values));
830 memset(nulls, false, sizeof(nulls));
831
844 CharGetDatum(opts.twophase ?
852 BoolGetDatum(opts.retaindeadtuples);
854 Int32GetDatum(opts.maxretention);
856 BoolGetDatum(opts.retaindeadtuples);
858 if (!OidIsValid(serverid))
860 CStringGetTextDatum(conninfo);
861 else
862 nulls[Anum_pg_subscription_subconninfo - 1] = true;
863 if (opts.slot_name)
866 else
867 nulls[Anum_pg_subscription_subslotname - 1] = true;
869 CStringGetTextDatum(opts.synchronous_commit);
871 CStringGetTextDatum(opts.wal_receiver_timeout);
873 publicationListToArray(publications);
876
879
880 /*
881 * We create the conflict log table here, if required, so that its
882 * relation OID can be stored when inserting the pg_subscription tuple
883 * below.
884 */
885 if (CONFLICTS_LOGGED_TO_TABLE(opts.conflictlogdest))
886 logrelid = create_conflict_log_table(subid, stmt->subname, owner);
887
888 /* Store table OID in the catalog. */
891
893
894 /* Insert tuple into catalog. */
897
899
901
902 if (stmt->servername)
903 {
905
906 Assert(OidIsValid(serverid));
907
910 }
911
912 /*
913 * Establish an internal dependency between the conflict log table and the
914 * subscription.
915 *
916 * We use DEPENDENCY_INTERNAL to signify that the table's lifecycle is
917 * strictly tied to the subscription, similar to how a TOAST table relates
918 * to its main table or a sequence relates to an identity column.
919 *
920 * This ensures the conflict log table is automatically reaped during a
921 * DROP SUBSCRIPTION via performDeletion().
922 */
923 if (OidIsValid(logrelid))
924 {
926
929 }
930
931 /*
932 * A replication origin is currently created for all subscriptions,
933 * including those that only contain sequences or are otherwise empty.
934 *
935 * XXX: While this is technically unnecessary, optimizing it would require
936 * additional logic to skip origin creation during DDL operations and
937 * apply workers initialization, and to handle origin creation dynamically
938 * when tables are added to the subscription. It is not clear whether
939 * preventing creation of origins is worth additional complexity.
940 */
943
944 /*
945 * Connect to remote side to execute requested commands and fetch table
946 * and sequence info.
947 */
948 if (opts.connect)
949 {
950 char *err;
953
954 /* Try to connect to the publisher. */
955 must_use_password = !superuser_arg(owner) && opts.passwordrequired;
956 wrconn = walrcv_connect(conninfo, true, true, must_use_password,
957 stmt->subname, &err);
958 if (!wrconn)
961 errmsg("subscription \"%s\" could not connect to the publisher: %s",
962 stmt->subname, err)));
963
964 PG_TRY();
965 {
966 bool has_tables = false;
967 List *pubrels;
968 char relation_state;
969
970 check_publications(wrconn, publications);
972 opts.copy_data,
973 opts.retaindeadtuples, opts.origin,
974 NULL, 0, stmt->subname);
976 opts.copy_data, opts.origin,
977 NULL, 0, stmt->subname);
978
979 if (opts.retaindeadtuples)
981
982 /*
983 * Set sync state based on if we were asked to do data copy or
984 * not.
985 */
987
988 /*
989 * Build local relation status info. Relations are for both tables
990 * and sequences from the publisher.
991 */
992 pubrels = fetch_relation_list(wrconn, publications);
993
995 {
996 Oid relid;
997 char relkind;
998 RangeVar *rv = pubrelinfo->rv;
999
1000 relid = RangeVarGetRelid(rv, AccessShareLock, false);
1001 relkind = get_rel_relkind(relid);
1002
1003 /* Check for supported relkind. */
1004 CheckSubscriptionRelkind(relkind, pubrelinfo->relkind,
1005 rv->schemaname, rv->relname);
1006 has_tables |= (relkind != RELKIND_SEQUENCE);
1008 InvalidXLogRecPtr, true);
1009 }
1010
1011 /*
1012 * If requested, create permanent slot for the subscription. We
1013 * won't use the initial snapshot for anything, so no need to
1014 * export it.
1015 *
1016 * XXX: Similar to origins, it is not clear whether preventing the
1017 * slot creation for empty and sequence-only subscriptions is
1018 * worth additional complexity.
1019 */
1020 if (opts.create_slot)
1021 {
1022 bool twophase_enabled = false;
1023
1024 Assert(opts.slot_name);
1025
1026 /*
1027 * Even if two_phase is set, don't create the slot with
1028 * two-phase enabled. Will enable it once all the tables are
1029 * synced and ready. This avoids race-conditions like prepared
1030 * transactions being skipped due to changes not being applied
1031 * due to checks in should_apply_changes_for_rel() when
1032 * tablesync for the corresponding tables are in progress. See
1033 * comments atop worker.c.
1034 *
1035 * Note that if tables were specified but copy_data is false
1036 * then it is safe to enable two_phase up-front because those
1037 * tables are already initially in READY state. When the
1038 * subscription has no tables, we leave the twophase state as
1039 * PENDING, to allow ALTER SUBSCRIPTION ... REFRESH
1040 * PUBLICATION to work.
1041 */
1042 if (opts.twophase && !opts.copy_data && has_tables)
1043 twophase_enabled = true;
1044
1046 opts.failover, CRS_NOEXPORT_SNAPSHOT, NULL);
1047
1048 if (twophase_enabled)
1050
1052 (errmsg("created replication slot \"%s\" on publisher",
1053 opts.slot_name)));
1054 }
1055 }
1056 PG_FINALLY();
1057 {
1059 }
1060 PG_END_TRY();
1061 }
1062 else
1064 (errmsg("subscription was created, but is not connected"),
1065 errhint("To initiate replication, you must manually create the replication slot, enable the subscription, and alter the subscription to refresh publications.")));
1066
1068
1070
1071 /*
1072 * Notify the launcher to start the apply worker if the subscription is
1073 * enabled, or to create the conflict detection slot if retain_dead_tuples
1074 * is enabled.
1075 *
1076 * Creating the conflict detection slot is essential even when the
1077 * subscription is not enabled. This ensures that dead tuples are
1078 * retained, which is necessary for accurately identifying the type of
1079 * conflict during replication.
1080 */
1081 if (opts.enabled || opts.retaindeadtuples)
1083
1085
1086 return myself;
1087}
bool has_privs_of_role(Oid member, Oid role)
Definition acl.c:5317
Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
Definition catalog.c:475
int errdetail(const char *fmt,...) pg_attribute_printf(1
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
Definition heaptuple.c:1025
void CatalogTupleInsert(Relation heapRel, HeapTuple tup)
Definition indexing.c:233
#define InvokeObjectPostCreateHook(classId, objectId, subId)
ReplOriginId replorigin_create(const char *roname)
Definition origin.c:274
@ OBJECT_FOREIGN_SERVER
void recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
void pgstat_create_subscription(Oid subid)
#define SUBOPT_CREATE_SLOT
#define SUBOPT_CONNECT
bool superuser_arg(Oid roleid)
Definition superuser.c:57
#define GetSysCacheOid2(cacheId, oidcol, key1, key2)
Definition syscache.h:111
void UpdateTwoPhaseState(Oid suboid, char new_state)
Definition tablesync.c:1681
#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
@ CRS_NOEXPORT_SNAPSHOT
Definition walsender.h:23

Referenced by ProcessUtilitySlow().

◆ defGetStreamingMode()

char defGetStreamingMode ( DefElem def)

Definition at line 3733 of file subscriptioncmds.c.

3734{
3735 /*
3736 * If no parameter value given, assume "true" is meant.
3737 */
3738 if (!def->arg)
3739 return LOGICALREP_STREAM_ON;
3740
3741 /*
3742 * Allow 0, 1, "false", "true", "off", "on" or "parallel".
3743 */
3744 switch (nodeTag(def->arg))
3745 {
3746 case T_Integer:
3747 switch (intVal(def->arg))
3748 {
3749 case 0:
3750 return LOGICALREP_STREAM_OFF;
3751 case 1:
3752 return LOGICALREP_STREAM_ON;
3753 default:
3754 /* otherwise, error out below */
3755 break;
3756 }
3757 break;
3758 default:
3759 {
3760 char *sval = defGetString(def);
3761
3762 /*
3763 * The set of strings accepted here should match up with the
3764 * grammar's opt_boolean_or_string production.
3765 */
3766 if (pg_strcasecmp(sval, "false") == 0 ||
3767 pg_strcasecmp(sval, "off") == 0)
3768 return LOGICALREP_STREAM_OFF;
3769 if (pg_strcasecmp(sval, "true") == 0 ||
3770 pg_strcasecmp(sval, "on") == 0)
3771 return LOGICALREP_STREAM_ON;
3772 if (pg_strcasecmp(sval, "parallel") == 0)
3774 }
3775 break;
3776 }
3777
3778 ereport(ERROR,
3780 errmsg("%s requires a Boolean value or \"parallel\"",
3781 def->defname)));
3782 return LOGICALREP_STREAM_OFF; /* keep compiler quiet */
3783}
char * defGetString(DefElem *def)
Definition define.c:34
#define nodeTag(nodeptr)
Definition nodes.h:137
char * defname
Definition parsenodes.h:862
Node * arg
Definition parsenodes.h:863
#define intVal(v)
Definition value.h:79

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

Referenced by parse_output_parameters(), and parse_subscription_options().

◆ drop_sub_conflict_log_table()

static void drop_sub_conflict_log_table ( Oid  subid,
char subname,
Oid  subconflictlogrelid 
)
static

Definition at line 2502 of file subscriptioncmds.c.

2503{
2504 /* Drop any dependent conflict log table */
2506 {
2507 ObjectAddress object;
2508 char *conflictrelname;
2509
2511 if (conflictrelname == NULL)
2512 elog(ERROR, "cache lookup failed for relation %u",
2514
2515 /*
2516 * By using PERFORM_DELETION_SKIP_ORIGINAL, we ensure that only the
2517 * conflict log table is deleted while the subscription remains.
2518 */
2523
2525 errmsg("dropped conflict log table \"%s\" for subscription \"%s\"",
2527 subname));
2528 }
2529}
void performDeletion(const ObjectAddress *object, DropBehavior behavior, int flags)
Definition dependency.c:279
#define PERFORM_DELETION_SKIP_ORIGINAL
Definition dependency.h:95
#define PERFORM_DELETION_INTERNAL
Definition dependency.h:92
char * get_qualified_objname(Oid nspid, char *objname)
Definition lsyscache.c:3720
@ DROP_CASCADE
Oid subconflictlogrelid

References DROP_CASCADE, elog, ereport, errmsg, ERROR, fb(), get_qualified_objname(), get_rel_name(), NOTICE, ObjectAddressSet, OidIsValid, PERFORM_DELETION_INTERNAL, PERFORM_DELETION_SKIP_ORIGINAL, performDeletion(), subconflictlogrelid, and subname.

Referenced by alter_sub_conflict_log_dest(), and DropSubscription().

◆ DropSubscription()

void DropSubscription ( DropSubscriptionStmt stmt,
bool  isTopLevel 
)

Definition at line 2535 of file subscriptioncmds.c.

2536{
2537 Relation rel;
2539 HeapTuple tup;
2540 Oid subid;
2541 Oid subowner;
2542 Oid subserver;
2544 char *subconninfo = NULL;
2545 Datum datum;
2546 bool isnull;
2547 char *subname;
2548 char *conninfo = NULL;
2549 char *slotname;
2551 ListCell *lc;
2552 char originname[NAMEDATALEN];
2553 char *err = NULL;
2556 List *rstates;
2557 bool must_use_password;
2558
2559 /*
2560 * The launcher may concurrently start a new worker for this subscription.
2561 * During initialization, the worker checks for subscription validity and
2562 * exits if the subscription has already been dropped. See
2563 * InitializeLogRepWorker.
2564 */
2566
2568 CStringGetDatum(stmt->subname));
2569
2570 if (!HeapTupleIsValid(tup))
2571 {
2572 table_close(rel, NoLock);
2573
2574 if (!stmt->missing_ok)
2575 ereport(ERROR,
2577 errmsg("subscription \"%s\" does not exist",
2578 stmt->subname)));
2579 else
2581 (errmsg("subscription \"%s\" does not exist, skipping",
2582 stmt->subname)));
2583
2584 return;
2585 }
2586
2589 if (!isnull)
2590 subconninfo = TextDatumGetCString(datum);
2591
2593 subid = form->oid;
2594 subowner = form->subowner;
2595 subserver = form->subserver;
2596 subconflictlogrelid = form->subconflictlogrelid;
2597 must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
2598
2599 /* must be owner */
2602 stmt->subname);
2603
2604 /* DROP hook for the subscription being removed */
2606
2607 /*
2608 * Lock the subscription so nobody else can do anything with it (including
2609 * the replication workers).
2610 */
2612
2613 /* Get subname */
2616 subname = pstrdup(NameStr(*DatumGetName(datum)));
2617
2618 /* Get slotname */
2621 if (!isnull)
2622 slotname = pstrdup(NameStr(*DatumGetName(datum)));
2623 else
2624 slotname = NULL;
2625
2626 /*
2627 * Since dropping a replication slot is not transactional, the replication
2628 * slot stays dropped even if the transaction rolls back. So we cannot
2629 * run DROP SUBSCRIPTION inside a transaction block if dropping the
2630 * replication slot. Also, in this case, we report a message for dropping
2631 * the subscription to the cumulative stats system.
2632 *
2633 * XXX The command name should really be something like "DROP SUBSCRIPTION
2634 * of a subscription that is associated with a replication slot", but we
2635 * don't have the proper facilities for that.
2636 */
2637 if (slotname)
2638 PreventInTransactionBlock(isTopLevel, "DROP SUBSCRIPTION");
2639
2642
2643 /* Remove the tuple from catalog. */
2644 CatalogTupleDelete(rel, &tup->t_self);
2645
2647
2648 /*
2649 * Stop all the subscription workers immediately.
2650 *
2651 * This is necessary if we are dropping the replication slot, so that the
2652 * slot becomes accessible.
2653 *
2654 * It is also necessary if the subscription is disabled and was disabled
2655 * in the same transaction. Then the workers haven't seen the disabling
2656 * yet and will still be running, leading to hangs later when we want to
2657 * drop the replication origin. If the subscription was disabled before
2658 * this transaction, then there shouldn't be any workers left, so this
2659 * won't make a difference.
2660 *
2661 * New workers won't be started because we hold an exclusive lock on the
2662 * subscription till the end of the transaction.
2663 */
2664 subworkers = logicalrep_workers_find(subid, false, true);
2665 foreach(lc, subworkers)
2666 {
2668
2670 }
2672
2673 /*
2674 * Remove the no-longer-useful entry in the launcher's table of apply
2675 * worker start times.
2676 *
2677 * If this transaction rolls back, the launcher might restart a failed
2678 * apply worker before wal_retrieve_retry_interval milliseconds have
2679 * elapsed, but that's pretty harmless.
2680 */
2682
2683 /*
2684 * Cleanup of tablesync replication origins.
2685 *
2686 * Any READY-state relations would already have dealt with clean-ups.
2687 *
2688 * Note that the state can't change because we have already stopped both
2689 * the apply and tablesync workers and they can't restart because of
2690 * exclusive lock on the subscription.
2691 */
2692 rstates = GetSubscriptionRelations(subid, true, false, true);
2693 foreach(lc, rstates)
2694 {
2696 Oid relid = rstate->relid;
2697
2698 /* Only cleanup resources of tablesync workers */
2699 if (!OidIsValid(relid))
2700 continue;
2701
2702 /*
2703 * Drop the tablesync's origin tracking if exists.
2704 *
2705 * It is possible that the origin is not yet created for tablesync
2706 * worker so passing missing_ok = true. This can happen for the states
2707 * before SUBREL_STATE_DATASYNC.
2708 */
2710 sizeof(originname));
2711 replorigin_drop_by_name(originname, true, false);
2712 }
2713
2714 /* Drop subscription's conflict log table */
2716
2717 /* Clean up dependencies */
2720
2721 /* Remove any associated relation synchronization states. */
2723
2724 /* Remove the origin tracking if exists. */
2726 replorigin_drop_by_name(originname, true, false);
2727
2728 /*
2729 * Tell the cumulative stats system that the subscription is getting
2730 * dropped.
2731 */
2733
2734 /*
2735 * If there is no slot associated with the subscription, we can finish
2736 * here.
2737 */
2738 if (!slotname && rstates == NIL)
2739 {
2740 table_close(rel, NoLock);
2741 return;
2742 }
2743
2744 /*
2745 * Try to acquire the connection necessary for dropping slots.
2746 *
2747 * Note: If the slotname is NONE/NULL then we allow the command to finish
2748 * and users need to manually cleanup the apply and tablesync worker slots
2749 * later.
2750 *
2751 * This has to be at the end because otherwise if there is an error while
2752 * doing the database operations we won't be able to rollback dropped
2753 * slot.
2754 */
2755 load_file("libpqwalreceiver", false);
2756
2757 if (OidIsValid(subserver))
2759 else
2760 conninfo = subconninfo;
2761
2762 if (conninfo)
2763 wrconn = walrcv_connect(conninfo, true, true, must_use_password,
2764 subname, &err);
2765
2766 if (wrconn == NULL)
2767 {
2768 if (!slotname)
2769 {
2770 /* be tidy */
2772 table_close(rel, NoLock);
2773 return;
2774 }
2775 else
2776 {
2777 ReportSlotConnectionError(rstates, subid, slotname, err);
2778 }
2779 }
2780
2781 PG_TRY();
2782 {
2783 foreach(lc, rstates)
2784 {
2786 Oid relid = rstate->relid;
2787
2788 /* Only cleanup resources of tablesync workers */
2789 if (!OidIsValid(relid))
2790 continue;
2791
2792 /*
2793 * Drop the tablesync slots associated with removed tables.
2794 *
2795 * For SYNCDONE/READY states, the tablesync slot is known to have
2796 * already been dropped by the tablesync worker.
2797 *
2798 * For other states, there is no certainty, maybe the slot does
2799 * not exist yet. Also, if we fail after removing some of the
2800 * slots, next time, it will again try to drop already dropped
2801 * slots and fail. For these reasons, we allow missing_ok = true
2802 * for the drop.
2803 */
2804 if (rstate->state != SUBREL_STATE_SYNCDONE)
2805 {
2806 char syncslotname[NAMEDATALEN] = {0};
2807
2809 sizeof(syncslotname));
2811 }
2812 }
2813
2815
2816 /*
2817 * If there is a slot associated with the subscription, then drop the
2818 * replication slot at the publisher.
2819 */
2820 if (slotname)
2821 ReplicationSlotDropAtPubNode(wrconn, slotname, false);
2822 }
2823 PG_FINALLY();
2824 {
2826 }
2827 PG_END_TRY();
2828
2829 table_close(rel, NoLock);
2830}
void EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal)
void CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid)
Definition indexing.c:365
void ApplyLauncherForgetWorkerStartTime(Oid subid)
Definition launcher.c:1155
void list_free(List *list)
Definition list.c:1546
char * pstrdup(const char *in)
Definition mcxt.c:1910
#define InvokeObjectDropHook(classId, objectId, subId)
long deleteDependencyRecordsFor(Oid classId, Oid objectId, bool skipExtensionDeps)
Definition pg_depend.c:314
void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
void pgstat_drop_subscription(Oid subid)
static Name DatumGetName(Datum X)
Definition postgres.h:393
LogicalRepWorkerType type
static char * construct_subserver_conninfo(Oid subserver, Oid subowner, char **err)
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err)
void ReleaseSysCache(HeapTuple tuple)
Definition syscache.c:265
HeapTuple SearchSysCache2(SysCacheIdentifier cacheId, Datum key1, Datum key2)
Definition syscache.c:231
Datum SysCacheGetAttrNotNull(SysCacheIdentifier cacheId, HeapTuple tup, AttrNumber attributeNumber)
Definition syscache.c:626
Datum SysCacheGetAttr(SysCacheIdentifier cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition syscache.c:596

References AccessExclusiveLock, aclcheck_error(), ACLCHECK_NOT_OWNER, ApplyLauncherForgetWorkerStartTime(), CatalogTupleDelete(), construct_subserver_conninfo(), CStringGetDatum(), DatumGetName(), deleteDependencyRecordsFor(), deleteSharedDependencyRecordsFor(), drop_sub_conflict_log_table(), ereport, err(), errcode(), errmsg, ERROR, EventTriggerSQLDropAddObject(), fb(), Form_pg_subscription, 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, subconflictlogrelid, LogicalRepWorker::subid, subname, superuser_arg(), SysCacheGetAttr(), SysCacheGetAttrNotNull(), table_close(), table_open(), TextDatumGetCString, LogicalRepWorker::type, walrcv_connect, walrcv_disconnect, and wrconn.

Referenced by ProcessUtilitySlow().

◆ fetch_relation_list()

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

Definition at line 3466 of file subscriptioncmds.c.

3467{
3468 WalRcvExecResult *res;
3469 StringInfoData cmd;
3470 TupleTableSlot *slot;
3474 bool check_columnlist = (server_version >= 150000);
3475 int column_count = check_columnlist ? 4 : 3;
3476 StringInfoData pub_names;
3477
3478 initStringInfo(&cmd);
3479 initStringInfo(&pub_names);
3480
3481 /* Build the pub_names comma-separated string. */
3482 GetPublicationsStr(publications, &pub_names, true);
3483
3484 /* Get the list of relations from the publisher */
3485 if (server_version >= 160000)
3486 {
3488
3489 /*
3490 * From version 16, we allowed passing multiple publications to the
3491 * function pg_get_publication_tables. This helped to filter out the
3492 * partition table whose ancestor is also published in this
3493 * publication array.
3494 *
3495 * Join pg_get_publication_tables with pg_publication to exclude
3496 * non-existing publications.
3497 *
3498 * Note that attrs are always stored in sorted order so we don't need
3499 * to worry if different publications have specified them in a
3500 * different order. See pub_collist_validate.
3501 */
3502 appendStringInfo(&cmd, "SELECT DISTINCT n.nspname, c.relname, c.relkind, gpt.attrs\n"
3503 " FROM pg_class c\n"
3504 " JOIN pg_namespace n ON n.oid = c.relnamespace\n"
3505 " JOIN ( SELECT (pg_get_publication_tables(VARIADIC array_agg(pubname::text))).*\n"
3506 " FROM pg_publication\n"
3507 " WHERE pubname IN ( %s )) AS gpt\n"
3508 " ON gpt.relid = c.oid\n",
3509 pub_names.data);
3510
3511 /* From version 19, inclusion of sequences in the target is supported */
3512 if (server_version >= 190000)
3513 appendStringInfo(&cmd,
3514 "UNION ALL\n"
3515 " SELECT DISTINCT s.schemaname, s.sequencename, " CppAsString2(RELKIND_SEQUENCE) "::\"char\" AS relkind, NULL::int2vector AS attrs\n"
3516 " FROM pg_catalog.pg_publication_sequences s\n"
3517 " WHERE s.pubname IN ( %s )",
3518 pub_names.data);
3519 }
3520 else
3521 {
3523 appendStringInfoString(&cmd, "SELECT DISTINCT t.schemaname, t.tablename, " CppAsString2(RELKIND_RELATION) "::\"char\" AS relkind \n");
3524
3525 /* Get column lists for each relation if the publisher supports it */
3526 if (check_columnlist)
3527 appendStringInfoString(&cmd, ", t.attnames\n");
3528
3529 appendStringInfo(&cmd, "FROM pg_catalog.pg_publication_tables t\n"
3530 " WHERE t.pubname IN ( %s )",
3531 pub_names.data);
3532 }
3533
3534 pfree(pub_names.data);
3535
3537 pfree(cmd.data);
3538
3539 if (res->status != WALRCV_OK_TUPLES)
3540 ereport(ERROR,
3542 errmsg("could not receive list of replicated tables from the publisher: %s",
3543 res->err)));
3544
3545 /* Process tables. */
3547 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3548 {
3549 char *nspname;
3550 char *relname;
3551 bool isnull;
3552 char relkind;
3554
3555 nspname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3556 Assert(!isnull);
3557 relname = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
3558 Assert(!isnull);
3559 relkind = DatumGetChar(slot_getattr(slot, 3, &isnull));
3560 Assert(!isnull);
3561
3562 relinfo->rv = makeRangeVar(nspname, relname, -1);
3563 relinfo->relkind = relkind;
3564
3565 if (relkind != RELKIND_SEQUENCE &&
3568 ereport(ERROR,
3570 errmsg("cannot use different column lists for table \"%s.%s\" in different publications",
3571 nspname, relname));
3572 else
3574
3575 ExecClearTuple(slot);
3576 }
3578
3580
3581 return relationlist;
3582}
#define CppAsString2(x)
Definition c.h:565
RangeVar * makeRangeVar(char *schemaname, char *relname, int location)
Definition makefuncs.c:473
static int server_version
Definition pg_dumpall.c:109
static char DatumGetChar(Datum X)
Definition postgres.h:122
static bool list_member_rangevar(const List *list, RangeVar *rv)

References appendStringInfo(), appendStringInfoString(), Assert, CppAsString2, StringInfoData::data, DatumGetChar(), ereport, WalRcvExecResult::err, errcode(), errmsg, ERROR, ExecClearTuple(), ExecDropSingleTupleTableSlot(), fb(), GetPublicationsStr(), initStringInfo(), InvalidOid, lappend(), list_member_rangevar(), makeRangeVar(), MakeSingleTupleTableSlot(), NIL, palloc_object, 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().

◆ list_member_rangevar()

static bool list_member_rangevar ( const List list,
RangeVar rv 
)
static

Definition at line 3445 of file subscriptioncmds.c.

3446{
3448 {
3449 if (equal(relinfo->rv, rv))
3450 return true;
3451 }
3452
3453 return false;
3454}
bool equal(const void *a, const void *b)
Definition equalfuncs.c:223

References equal(), fb(), and foreach_ptr.

Referenced by fetch_relation_list().

◆ merge_publications()

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

Definition at line 3674 of file subscriptioncmds.c.

3675{
3676 ListCell *lc;
3677
3679
3681
3682 foreach(lc, newpublist)
3683 {
3684 char *name = strVal(lfirst(lc));
3685 ListCell *lc2;
3686 bool found = false;
3687
3688 foreach(lc2, oldpublist)
3689 {
3690 char *pubname = strVal(lfirst(lc2));
3691
3692 if (strcmp(name, pubname) == 0)
3693 {
3694 found = true;
3695 if (addpub)
3696 ereport(ERROR,
3698 errmsg("publication \"%s\" is already in subscription \"%s\"",
3699 name, subname)));
3700 else
3702
3703 break;
3704 }
3705 }
3706
3707 if (addpub && !found)
3709 else if (!addpub && !found)
3710 ereport(ERROR,
3712 errmsg("publication \"%s\" is not in subscription \"%s\"",
3713 name, subname)));
3714 }
3715
3716 /*
3717 * XXX Probably no strong reason for this, but for now it's to make ALTER
3718 * SUBSCRIPTION ... DROP PUBLICATION consistent with SET PUBLICATION.
3719 */
3720 if (!oldpublist)
3721 ereport(ERROR,
3723 errmsg("cannot drop all the publications from a subscription")));
3724
3725 return oldpublist;
3726}
#define foreach_delete_current(lst, var_or_cell)
Definition pg_list.h:423
static void check_duplicates_in_publist(List *publist, Datum *datums)

References check_duplicates_in_publist(), ereport, errcode(), ERRCODE_DUPLICATE_OBJECT, errmsg, ERROR, fb(), 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,
uint32  supported_opts,
SubOpts opts 
)
static

Definition at line 162 of file subscriptioncmds.c.

164{
165 ListCell *lc;
166
167 /* Start out with cleared opts. */
168 memset(opts, 0, sizeof(SubOpts));
169
170 /* caller must expect some option */
172
173 /* If connect option is supported, these others also need to be. */
177
178 /* Set default values for the supported options. */
180 opts->connect = true;
182 opts->enabled = true;
184 opts->create_slot = true;
186 opts->copy_data = true;
188 opts->refresh = true;
190 opts->binary = false;
192 opts->streaming = LOGICALREP_STREAM_PARALLEL;
194 opts->twophase = false;
196 opts->disableonerr = false;
198 opts->passwordrequired = true;
200 opts->runasowner = false;
202 opts->failover = false;
204 opts->retaindeadtuples = false;
206 opts->maxretention = 0;
210 opts->conflictlogdest = CONFLICT_LOG_DEST_LOG;
211
212 /* Parse options */
213 foreach(lc, stmt_options)
214 {
215 DefElem *defel = (DefElem *) lfirst(lc);
216
218 strcmp(defel->defname, "connect") == 0)
219 {
220 if (IsSet(opts->specified_opts, SUBOPT_CONNECT))
222
223 opts->specified_opts |= SUBOPT_CONNECT;
224 opts->connect = defGetBoolean(defel);
225 }
227 strcmp(defel->defname, "enabled") == 0)
228 {
229 if (IsSet(opts->specified_opts, SUBOPT_ENABLED))
231
232 opts->specified_opts |= SUBOPT_ENABLED;
233 opts->enabled = defGetBoolean(defel);
234 }
236 strcmp(defel->defname, "create_slot") == 0)
237 {
238 if (IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
240
241 opts->specified_opts |= SUBOPT_CREATE_SLOT;
242 opts->create_slot = defGetBoolean(defel);
243 }
245 strcmp(defel->defname, "slot_name") == 0)
246 {
247 if (IsSet(opts->specified_opts, SUBOPT_SLOT_NAME))
249
250 opts->specified_opts |= SUBOPT_SLOT_NAME;
251 opts->slot_name = defGetString(defel);
252
253 /* Setting slot_name = NONE is treated as no slot name. */
254 if (strcmp(opts->slot_name, "none") == 0)
255 opts->slot_name = NULL;
256 else
257 ReplicationSlotValidateName(opts->slot_name, false, ERROR);
258 }
260 strcmp(defel->defname, "copy_data") == 0)
261 {
262 if (IsSet(opts->specified_opts, SUBOPT_COPY_DATA))
264
265 opts->specified_opts |= SUBOPT_COPY_DATA;
266 opts->copy_data = defGetBoolean(defel);
267 }
269 strcmp(defel->defname, "synchronous_commit") == 0)
270 {
271 if (IsSet(opts->specified_opts, SUBOPT_SYNCHRONOUS_COMMIT))
273
274 opts->specified_opts |= SUBOPT_SYNCHRONOUS_COMMIT;
275 opts->synchronous_commit = defGetString(defel);
276
277 /* Test if the given value is valid for synchronous_commit GUC. */
278 (void) set_config_option("synchronous_commit", opts->synchronous_commit,
280 false, 0, false);
281 }
283 strcmp(defel->defname, "refresh") == 0)
284 {
285 if (IsSet(opts->specified_opts, SUBOPT_REFRESH))
287
288 opts->specified_opts |= SUBOPT_REFRESH;
289 opts->refresh = defGetBoolean(defel);
290 }
292 strcmp(defel->defname, "binary") == 0)
293 {
294 if (IsSet(opts->specified_opts, SUBOPT_BINARY))
296
297 opts->specified_opts |= SUBOPT_BINARY;
298 opts->binary = defGetBoolean(defel);
299 }
301 strcmp(defel->defname, "streaming") == 0)
302 {
303 if (IsSet(opts->specified_opts, SUBOPT_STREAMING))
305
306 opts->specified_opts |= SUBOPT_STREAMING;
307 opts->streaming = defGetStreamingMode(defel);
308 }
310 strcmp(defel->defname, "two_phase") == 0)
311 {
312 if (IsSet(opts->specified_opts, SUBOPT_TWOPHASE_COMMIT))
314
315 opts->specified_opts |= SUBOPT_TWOPHASE_COMMIT;
316 opts->twophase = defGetBoolean(defel);
317 }
319 strcmp(defel->defname, "disable_on_error") == 0)
320 {
321 if (IsSet(opts->specified_opts, SUBOPT_DISABLE_ON_ERR))
323
324 opts->specified_opts |= SUBOPT_DISABLE_ON_ERR;
325 opts->disableonerr = defGetBoolean(defel);
326 }
328 strcmp(defel->defname, "password_required") == 0)
329 {
330 if (IsSet(opts->specified_opts, SUBOPT_PASSWORD_REQUIRED))
332
333 opts->specified_opts |= SUBOPT_PASSWORD_REQUIRED;
334 opts->passwordrequired = defGetBoolean(defel);
335 }
337 strcmp(defel->defname, "run_as_owner") == 0)
338 {
339 if (IsSet(opts->specified_opts, SUBOPT_RUN_AS_OWNER))
341
342 opts->specified_opts |= SUBOPT_RUN_AS_OWNER;
343 opts->runasowner = defGetBoolean(defel);
344 }
346 strcmp(defel->defname, "failover") == 0)
347 {
348 if (IsSet(opts->specified_opts, SUBOPT_FAILOVER))
350
351 opts->specified_opts |= SUBOPT_FAILOVER;
352 opts->failover = defGetBoolean(defel);
353 }
355 strcmp(defel->defname, "retain_dead_tuples") == 0)
356 {
357 if (IsSet(opts->specified_opts, SUBOPT_RETAIN_DEAD_TUPLES))
359
360 opts->specified_opts |= SUBOPT_RETAIN_DEAD_TUPLES;
361 opts->retaindeadtuples = defGetBoolean(defel);
362 }
364 strcmp(defel->defname, "max_retention_duration") == 0)
365 {
366 if (IsSet(opts->specified_opts, SUBOPT_MAX_RETENTION_DURATION))
368
369 opts->specified_opts |= SUBOPT_MAX_RETENTION_DURATION;
370 opts->maxretention = defGetInt32(defel);
371
372 if (opts->maxretention < 0)
375 errmsg("max_retention_duration cannot be negative"));
376 }
378 strcmp(defel->defname, "origin") == 0)
379 {
380 if (IsSet(opts->specified_opts, SUBOPT_ORIGIN))
382
383 opts->specified_opts |= SUBOPT_ORIGIN;
384 pfree(opts->origin);
385
386 /*
387 * Even though the "origin" parameter allows only "none" and "any"
388 * values, it is implemented as a string type so that the
389 * parameter can be extended in future versions to support
390 * filtering using origin names specified by the user.
391 */
392 opts->origin = defGetString(defel);
393
394 if ((pg_strcasecmp(opts->origin, LOGICALREP_ORIGIN_NONE) != 0) &&
398 errmsg("unrecognized origin value: \"%s\"", opts->origin));
399 }
400 else if (IsSet(supported_opts, SUBOPT_LSN) &&
401 strcmp(defel->defname, "lsn") == 0)
402 {
403 char *lsn_str = defGetString(defel);
404 XLogRecPtr lsn;
405
406 if (IsSet(opts->specified_opts, SUBOPT_LSN))
408
409 /* Setting lsn = NONE is treated as resetting LSN */
410 if (strcmp(lsn_str, "none") == 0)
411 lsn = InvalidXLogRecPtr;
412 else
413 {
414 /* Parse the argument as LSN */
417
418 if (!XLogRecPtrIsValid(lsn))
421 errmsg("invalid WAL location (LSN): %s", lsn_str)));
422 }
423
424 opts->specified_opts |= SUBOPT_LSN;
425 opts->lsn = lsn;
426 }
428 strcmp(defel->defname, "wal_receiver_timeout") == 0)
429 {
430 bool parsed;
431 int val;
432
433 if (IsSet(opts->specified_opts, SUBOPT_WAL_RECEIVER_TIMEOUT))
435
436 opts->specified_opts |= SUBOPT_WAL_RECEIVER_TIMEOUT;
437 opts->wal_receiver_timeout = defGetString(defel);
438
439 /*
440 * Test if the given value is valid for wal_receiver_timeout GUC.
441 * Skip this test if the value is -1, since -1 is allowed for the
442 * wal_receiver_timeout subscription option, but not for the GUC
443 * itself.
444 */
445 parsed = parse_int(opts->wal_receiver_timeout, &val, 0, NULL);
446 if (!parsed || val != -1)
447 (void) set_config_option("wal_receiver_timeout", opts->wal_receiver_timeout,
449 false, 0, false);
450 }
452 strcmp(defel->defname, "conflict_log_destination") == 0)
453 {
454 char *val;
455
456 if (IsSet(opts->specified_opts, SUBOPT_CONFLICT_LOG_DEST))
458
460 opts->conflictlogdest = GetConflictLogDest(val);
461 opts->specified_opts |= SUBOPT_CONFLICT_LOG_DEST;
462 }
463 else
466 errmsg("unrecognized subscription parameter: \"%s\"", defel->defname)));
467 }
468
469 /*
470 * We've been explicitly asked to not connect, that requires some
471 * additional processing.
472 */
473 if (!opts->connect && IsSet(supported_opts, SUBOPT_CONNECT))
474 {
475 /* Check for incompatible options from the user. */
476 if (opts->enabled &&
477 IsSet(opts->specified_opts, SUBOPT_ENABLED))
480 /*- translator: both %s are strings of the form "option = value" */
481 errmsg("%s and %s are mutually exclusive options",
482 "connect = false", "enabled = true")));
483
484 if (opts->create_slot &&
485 IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
488 errmsg("%s and %s are mutually exclusive options",
489 "connect = false", "create_slot = true")));
490
491 if (opts->copy_data &&
492 IsSet(opts->specified_opts, SUBOPT_COPY_DATA))
495 errmsg("%s and %s are mutually exclusive options",
496 "connect = false", "copy_data = true")));
497
498 /* Change the defaults of other options. */
499 opts->enabled = false;
500 opts->create_slot = false;
501 opts->copy_data = false;
502 }
503
504 /*
505 * Do additional checking for disallowed combination when slot_name = NONE
506 * was used.
507 */
508 if (!opts->slot_name &&
509 IsSet(opts->specified_opts, SUBOPT_SLOT_NAME))
510 {
511 if (opts->enabled)
512 {
513 if (IsSet(opts->specified_opts, SUBOPT_ENABLED))
516 /*- translator: both %s are strings of the form "option = value" */
517 errmsg("%s and %s are mutually exclusive options",
518 "slot_name = NONE", "enabled = true")));
519 else
522 /*- translator: both %s are strings of the form "option = value" */
523 errmsg("subscription with %s must also set %s",
524 "slot_name = NONE", "enabled = false")));
525 }
526
527 if (opts->create_slot)
528 {
529 if (IsSet(opts->specified_opts, SUBOPT_CREATE_SLOT))
532 /*- translator: both %s are strings of the form "option = value" */
533 errmsg("%s and %s are mutually exclusive options",
534 "slot_name = NONE", "create_slot = true")));
535 else
538 /*- translator: both %s are strings of the form "option = value" */
539 errmsg("subscription with %s must also set %s",
540 "slot_name = NONE", "create_slot = false")));
541 }
542 }
543}
@ CONFLICT_LOG_DEST_LOG
Definition conflict.h:91
int32 defGetInt32(DefElem *def)
Definition define.c:148
bool defGetBoolean(DefElem *def)
Definition define.c:93
void errorConflictingDefElem(DefElem *defel, ParseState *pstate)
Definition define.c:370
bool parse_int(const char *value, int *result, int flags, const char **hintmsg)
Definition guc.c:2775
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:3248
@ GUC_ACTION_SET
Definition guc.h:203
@ PGC_S_TEST
Definition guc.h:125
@ PGC_BACKEND
Definition guc.h:77
long val
Definition informix.c:689
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:265
char defGetStreamingMode(DefElem *def)

References Assert, CONFLICT_LOG_DEST_LOG, CStringGetDatum(), DatumGetLSN(), defGetBoolean(), defGetInt32(), defGetStreamingMode(), defGetString(), DirectFunctionCall1, ereport, errcode(), errmsg, ERROR, errorConflictingDefElem(), fb(), GetConflictLogDest(), GUC_ACTION_SET, InvalidXLogRecPtr, IsSet, lfirst, opts, parse_int(), pfree(), pg_lsn_in(), pg_strcasecmp(), PGC_BACKEND, PGC_S_TEST, pstrdup(), ReplicationSlotValidateName(), set_config_option(), SUBOPT_BINARY, SUBOPT_CONFLICT_LOG_DEST, 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, SUBOPT_WAL_RECEIVER_TIMEOUT, val, and XLogRecPtrIsValid.

Referenced by AlterSubscription().

◆ publicationListToArray()

static Datum publicationListToArray ( List publist)
static

Definition at line 640 of file subscriptioncmds.c.

641{
642 ArrayType *arr;
643 Datum *datums;
644 MemoryContext memcxt;
646
647 /* Create memory context for temporary allocations. */
649 "publicationListToArray to array",
652
654
656
658
660
661 MemoryContextDelete(memcxt);
662
663 return PointerGetDatum(arr);
664}
ArrayType * construct_array_builtin(Datum *elems, int nelems, Oid elmtype)
#define palloc_array(type, count)
Definition fe_memutils.h:91
MemoryContext CurrentMemoryContext
Definition mcxt.c:161
void MemoryContextDelete(MemoryContext context)
Definition mcxt.c:475
#define AllocSetContextCreate
Definition memutils.h:129
#define ALLOCSET_DEFAULT_SIZES
Definition memutils.h:160
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition palloc.h:138
#define PointerGetDatum(X)
Definition postgres.h:354

Referenced by AlterSubscription().

◆ ReplicationSlotDropAtPubNode()

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

Definition at line 2840 of file subscriptioncmds.c.

2841{
2842 StringInfoData cmd;
2843
2844 Assert(wrconn);
2845
2846 load_file("libpqwalreceiver", false);
2847
2848 initStringInfo(&cmd);
2849 appendStringInfoString(&cmd, "DROP_REPLICATION_SLOT ");
2850 appendQuotedIdentifier(&cmd, slotname);
2851 appendStringInfoString(&cmd, " WAIT");
2852
2853 PG_TRY();
2854 {
2855 WalRcvExecResult *res;
2856
2857 res = walrcv_exec(wrconn, cmd.data, 0, NULL);
2858
2859 if (res->status == WALRCV_OK_COMMAND)
2860 {
2861 /* NOTICE. Success. */
2863 (errmsg("dropped replication slot \"%s\" on publisher",
2864 slotname)));
2865 }
2866 else if (res->status == WALRCV_ERROR &&
2867 missing_ok &&
2869 {
2870 /* LOG. Error, but missing_ok = true. */
2871 ereport(LOG,
2872 (errmsg("could not drop replication slot \"%s\" on publisher: %s",
2873 slotname, res->err)));
2874 }
2875 else
2876 {
2877 /* ERROR. */
2878 ereport(ERROR,
2880 errmsg("could not drop replication slot \"%s\" on publisher: %s",
2881 slotname, res->err)));
2882 }
2883
2885 }
2886 PG_FINALLY();
2887 {
2888 pfree(cmd.data);
2889 }
2890 PG_END_TRY();
2891}
#define LOG
Definition elog.h:32
#define appendQuotedIdentifier(b, s)
@ WALRCV_OK_COMMAND
@ WALRCV_ERROR

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

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

◆ ReportSlotConnectionError()

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

Definition at line 3590 of file subscriptioncmds.c.

3591{
3592 ListCell *lc;
3593
3594 foreach(lc, rstates)
3595 {
3597 Oid relid = rstate->relid;
3598
3599 /* Only cleanup resources of tablesync workers */
3600 if (!OidIsValid(relid))
3601 continue;
3602
3603 /*
3604 * Caller needs to ensure that relstate doesn't change underneath us.
3605 * See DropSubscription where we get the relstates.
3606 */
3607 if (rstate->state != SUBREL_STATE_SYNCDONE)
3608 {
3609 char syncslotname[NAMEDATALEN] = {0};
3610
3612 sizeof(syncslotname));
3613 elog(WARNING, "could not drop tablesync replication slot \"%s\"",
3614 syncslotname);
3615 }
3616 }
3617
3618 ereport(ERROR,
3620 errmsg("could not connect to publisher when attempting to drop replication slot \"%s\": %s",
3621 slotname, err),
3622 /* translator: %s is an SQL ALTER command */
3623 errhint("Use %s to disable the subscription, and then use %s to disassociate it from the slot.",
3624 "ALTER SUBSCRIPTION ... DISABLE",
3625 "ALTER SUBSCRIPTION ... SET (slot_name = NONE)")));
3626}

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

Referenced by DropSubscription().