PostgreSQL Source Code git master
Loading...
Searching...
No Matches
subscriptioncmds.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * subscriptioncmds.c
4 * subscription catalog manipulation functions
5 *
6 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 * IDENTIFICATION
10 * src/backend/commands/subscriptioncmds.c
11 *
12 *-------------------------------------------------------------------------
13 */
14
15#include "postgres.h"
16
17#include "access/commit_ts.h"
18#include "access/htup_details.h"
19#include "access/table.h"
20#include "access/twophase.h"
21#include "access/xact.h"
22#include "catalog/catalog.h"
23#include "catalog/dependency.h"
24#include "catalog/indexing.h"
25#include "catalog/namespace.h"
28#include "catalog/pg_authid_d.h"
29#include "catalog/pg_database_d.h"
34#include "catalog/pg_type.h"
36#include "commands/defrem.h"
39#include "commands/tablecmds.h"
40#include "executor/executor.h"
41#include "foreign/foreign.h"
42#include "miscadmin.h"
43#include "nodes/makefuncs.h"
44#include "pgstat.h"
47#include "replication/origin.h"
48#include "replication/slot.h"
52#include "storage/lmgr.h"
53#include "storage/lock.h"
54#include "utils/acl.h"
55#include "utils/builtins.h"
56#include "utils/guc.h"
57#include "utils/lsyscache.h"
58#include "utils/memutils.h"
59#include "utils/pg_lsn.h"
60#include "utils/syscache.h"
61
62/*
63 * Options that can be specified by the user in CREATE/ALTER SUBSCRIPTION
64 * command.
65 */
66#define SUBOPT_CONNECT 0x00000001
67#define SUBOPT_ENABLED 0x00000002
68#define SUBOPT_CREATE_SLOT 0x00000004
69#define SUBOPT_SLOT_NAME 0x00000008
70#define SUBOPT_COPY_DATA 0x00000010
71#define SUBOPT_SYNCHRONOUS_COMMIT 0x00000020
72#define SUBOPT_REFRESH 0x00000040
73#define SUBOPT_BINARY 0x00000080
74#define SUBOPT_STREAMING 0x00000100
75#define SUBOPT_TWOPHASE_COMMIT 0x00000200
76#define SUBOPT_DISABLE_ON_ERR 0x00000400
77#define SUBOPT_PASSWORD_REQUIRED 0x00000800
78#define SUBOPT_RUN_AS_OWNER 0x00001000
79#define SUBOPT_FAILOVER 0x00002000
80#define SUBOPT_RETAIN_DEAD_TUPLES 0x00004000
81#define SUBOPT_MAX_RETENTION_DURATION 0x00008000
82#define SUBOPT_WAL_RECEIVER_TIMEOUT 0x00010000
83#define SUBOPT_LSN 0x00020000
84#define SUBOPT_ORIGIN 0x00040000
85#define SUBOPT_CONFLICT_LOG_DEST 0x00080000
86
87/* check if the 'val' has 'bits' set */
88#define IsSet(val, bits) (((val) & (bits)) == (bits))
89
90/*
91 * Structure to hold a bitmap representing the user-provided CREATE/ALTER
92 * SUBSCRIPTION command options and the parsed/default values of each of them.
93 */
118
119/*
120 * PublicationRelKind represents a relation included in a publication.
121 * It stores the schema-qualified relation name (rv) and its kind (relkind).
122 */
128
129static List *fetch_relation_list(WalReceiverConn *wrconn, List *publications);
131 List *publications, bool copydata,
133 char *origin,
135 int subrel_count, char *subname);
137 List *publications,
138 bool copydata, char *origin,
140 int subrel_count,
141 char *subname);
143static void check_duplicates_in_publist(List *publist, Datum *datums);
144static List *merge_publications(List *oldpublist, List *newpublist, bool addpub, const char *subname);
145static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err);
146static void CheckAlterSubOption(Subscription *sub, const char *option,
147 bool slot_needs_update, bool isTopLevel);
152static void drop_sub_conflict_log_table(Oid subid, char *subname,
154
155/*
156 * Common option parsing function for CREATE and ALTER SUBSCRIPTION commands.
157 *
158 * Since not all options can be specified in both commands, this function
159 * will report an error if mutually exclusive options are specified.
160 */
161static void
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}
544
545/*
546 * Append a suitably-quoted identifier or string literal to buf.
547 * "quote" should be either a double-quote or single-quote character.
548 *
549 * Caution: this quoting logic is sufficient for identifiers and literals
550 * in the replication grammar, but not always in regular SQL. Specifically,
551 * it'd fail for a string literal if standard_conforming_strings is off.
552 */
553static void
554appendQuotedString(StringInfo buf, const char *str, char quote)
555{
557 while (*str)
558 {
559 char c = *str++;
560
561 if (c == quote)
564 }
566}
567
568#define appendQuotedIdentifier(b, s) appendQuotedString(b, s, '"')
569#define appendQuotedLiteral(b, s) appendQuotedString(b, s, '\'')
570
571/*
572 * Check that the specified publications are present on the publisher.
573 */
574static void
575check_publications(WalReceiverConn *wrconn, List *publications)
576{
577 WalRcvExecResult *res;
578 StringInfoData cmd;
579 TupleTableSlot *slot;
580 List *publicationsCopy = NIL;
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)
594 ereport(ERROR,
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). */
601 slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
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. */
611 publicationsCopy = list_delete(publicationsCopy, makeString(pubname));
612 ExecClearTuple(slot);
613 }
614
615 ExecDropSingleTupleTableSlot(slot);
616
617 walrcv_clear_result(res);
618
619 if (list_length(publicationsCopy))
620 {
621 /* Prepare the list of non-existent publication(s) for error message. */
622 StringInfoData pubnames;
623
624 initStringInfo(&pubnames);
625
626 GetPublicationsStr(publicationsCopy, &pubnames, false);
627 ereport(WARNING,
628 errcode(ERRCODE_UNDEFINED_OBJECT),
629 errmsg_plural("publication %s does not exist on the publisher",
630 "publications %s do not exist on the publisher",
631 list_length(publicationsCopy),
632 pubnames.data));
633 }
634}
635
636/*
637 * Auxiliary function to build a text array out of a list of String nodes.
638 */
639static Datum
640publicationListToArray(List *publist)
641{
642 ArrayType *arr;
643 Datum *datums;
644 MemoryContext memcxt;
645 MemoryContext oldcxt;
646
647 /* Create memory context for temporary allocations. */
648 memcxt = AllocSetContextCreate(CurrentMemoryContext,
649 "publicationListToArray to array",
650 ALLOCSET_DEFAULT_SIZES);
651 oldcxt = MemoryContextSwitchTo(memcxt);
652
653 datums = palloc_array(Datum, list_length(publist));
654
655 check_duplicates_in_publist(publist, datums);
656
657 MemoryContextSwitchTo(oldcxt);
658
659 arr = construct_array_builtin(datums, list_length(publist), TEXTOID);
660
661 MemoryContextDelete(memcxt);
662
663 return PointerGetDatum(arr);
664}
665
666/*
667 * Create new subscription.
668 */
669ObjectAddress
670CreateSubscription(ParseState *pstate, CreateSubscriptionStmt *stmt,
671 bool isTopLevel)
672{
673 Relation rel;
674 ObjectAddress myself;
675 Oid subid;
676 bool nulls[Natts_pg_subscription];
677 Datum values[Natts_pg_subscription];
678 Oid owner = GetUserId();
679 HeapTuple tup;
680 Oid serverid;
681 char *conninfo;
682 char originname[NAMEDATALEN];
683 List *publications;
684 uint32 supported_opts;
685 SubOpts opts = {0};
686 AclResult aclresult;
687 Oid logrelid = InvalidOid;
688
689 /*
690 * Parse and check options.
691 *
692 * Connection and publication should not be specified here.
693 */
694 supported_opts = (SUBOPT_CONNECT | SUBOPT_ENABLED | SUBOPT_CREATE_SLOT |
695 SUBOPT_SLOT_NAME | SUBOPT_COPY_DATA |
696 SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY |
697 SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT |
698 SUBOPT_DISABLE_ON_ERR | SUBOPT_PASSWORD_REQUIRED |
699 SUBOPT_RUN_AS_OWNER | SUBOPT_FAILOVER |
700 SUBOPT_RETAIN_DEAD_TUPLES |
701 SUBOPT_MAX_RETENTION_DURATION |
702 SUBOPT_WAL_RECEIVER_TIMEOUT | SUBOPT_ORIGIN |
703 SUBOPT_CONFLICT_LOG_DEST);
704 parse_subscription_options(pstate, stmt->options, supported_opts, &opts);
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 */
720 if (!has_privs_of_role(owner, ROLE_PG_CREATE_SUBSCRIPTION))
721 ereport(ERROR,
722 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
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}
1088
1089static void
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,
1206 InvalidXLogRecPtr, true);
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}
1356
1357/*
1358 * Marks all sequences with INIT state.
1359 */
1360static void
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 {
1384 /*
1385 * Sequence synchronization depends on publisher-side functionality
1386 * introduced in PostgreSQL 19, so it cannot work against an older
1387 * publisher.
1388 */
1389 if (walrcv_server_version(wrconn) < 190000)
1390 ereport(ERROR,
1392 errmsg("cannot synchronize sequences if the publisher is running a version earlier than PostgreSQL 19"));
1393
1395 sub->origin, NULL, 0, sub->name);
1396 }
1397 PG_FINALLY();
1398 {
1400 }
1401 PG_END_TRY();
1402
1403 /*
1404 * Reset the sequences to INIT so they get re-synchronized with the latest
1405 * publisher values.
1406 *
1407 * A sequence sync worker may already be running. If it has fetched a
1408 * sequence's value from the publisher but not yet marked it READY, it
1409 * must not be allowed to complete that update, as it would overwrite the
1410 * reset below with a stale value and silently lose this refresh request.
1411 * So we stop any running sequence sync worker before resetting the
1412 * states.
1413 *
1414 * This is race-free because AlterSubscription() already holds
1415 * AccessExclusiveLock on the subscription object. That lock blocks a
1416 * running worker's update of sequence state to READY, see
1417 * UpdateSubscriptionRelState() which takes AccessShareLock on the object.
1418 * It also blocks any worker the apply worker re-launches, because a new
1419 * worker takes AccessShareLock on the object before it reads
1420 * pg_subscription_rel, see InitializeLogRepWorker(). Such a worker cannot
1421 * act on the states until we commit, by which time they are reset to INIT
1422 * and it will sync the latest values.
1423 */
1424#ifdef USE_ASSERT_CHECKING
1425 {
1426 LOCKTAG tag;
1427
1430 }
1431#endif
1432
1434
1435 /* Reset every local sequence of this subscription to INIT. */
1436 subrel_states = GetSubscriptionRelations(sub->oid, false, true, false);
1438 {
1439 Oid relid = subrel->relid;
1440
1442 InvalidXLogRecPtr, false);
1444 errmsg_internal("sequence \"%s.%s\" of subscription \"%s\" set to INIT state",
1446 get_rel_name(relid),
1447 sub->name));
1448 }
1449}
1450
1451/*
1452 * Common checks for altering failover, two_phase, and retain_dead_tuples
1453 * options.
1454 */
1455static void
1457 bool slot_needs_update, bool isTopLevel)
1458{
1459 Assert(strcmp(option, "failover") == 0 ||
1460 strcmp(option, "two_phase") == 0 ||
1461 strcmp(option, "retain_dead_tuples") == 0);
1462
1463 /*
1464 * Altering the retain_dead_tuples option does not update the slot on the
1465 * publisher.
1466 */
1467 Assert(!slot_needs_update || strcmp(option, "retain_dead_tuples") != 0);
1468
1469 /*
1470 * Do not allow changing the option if the subscription is enabled. This
1471 * is because both failover and two_phase options of the slot on the
1472 * publisher cannot be modified if the slot is currently acquired by the
1473 * existing walsender.
1474 *
1475 * Note that two_phase is enabled (aka changed from 'false' to 'true') on
1476 * the publisher by the existing walsender, so we could have allowed that
1477 * even when the subscription is enabled. But we kept this restriction for
1478 * the sake of consistency and simplicity.
1479 *
1480 * Additionally, do not allow changing the retain_dead_tuples option when
1481 * the subscription is enabled to prevent race conditions arising from the
1482 * new option value being acknowledged asynchronously by the launcher and
1483 * apply workers.
1484 *
1485 * Without the restriction, a race condition may arise when a user
1486 * disables and immediately re-enables the retain_dead_tuples option. In
1487 * this case, the launcher might drop the slot upon noticing the disabled
1488 * action, while the apply worker may keep maintaining
1489 * oldest_nonremovable_xid without noticing the option change. During this
1490 * period, a transaction ID wraparound could falsely make this ID appear
1491 * as if it originates from the future w.r.t the transaction ID stored in
1492 * the slot maintained by launcher.
1493 *
1494 * Similarly, if the user enables retain_dead_tuples concurrently with the
1495 * launcher starting the worker, the apply worker may start calculating
1496 * oldest_nonremovable_xid before the launcher notices the enable action.
1497 * Consequently, the launcher may update slot.xmin to a newer value than
1498 * that maintained by the worker. In subsequent cycles, upon integrating
1499 * the worker's oldest_nonremovable_xid, the launcher might detect a
1500 * retreat in the calculated xmin, necessitating additional handling.
1501 *
1502 * XXX To address the above race conditions, we can define
1503 * oldest_nonremovable_xid as FullTransactionId and adds the check to
1504 * disallow retreating the conflict slot's xmin. For now, we kept the
1505 * implementation simple by disallowing change to the retain_dead_tuples,
1506 * but in the future we can change this after some more analysis.
1507 *
1508 * Note that we could restrict only the enabling of retain_dead_tuples to
1509 * avoid the race conditions described above, but we maintain the
1510 * restriction for both enable and disable operations for the sake of
1511 * consistency.
1512 */
1513 if (sub->enabled)
1514 ereport(ERROR,
1516 errmsg("cannot set option \"%s\" for enabled subscription",
1517 option)));
1518
1520 {
1521 StringInfoData cmd;
1522
1523 /*
1524 * A valid slot must be associated with the subscription for us to
1525 * modify any of the slot's properties.
1526 */
1527 if (!sub->slotname)
1528 ereport(ERROR,
1530 errmsg("cannot set option \"%s\" for a subscription that does not have a slot name",
1531 option)));
1532
1533 /* The changed option of the slot can't be rolled back. */
1534 initStringInfo(&cmd);
1535 appendStringInfo(&cmd, "ALTER SUBSCRIPTION ... SET (%s)", option);
1536
1538 pfree(cmd.data);
1539 }
1540}
1541
1542/*
1543 * alter_sub_conflict_log_dest
1544 *
1545 * When the subscription's 'conflict_log_destination' is changed, update the
1546 * conflict log table if required.
1547 *
1548 * If the new destination no longer requires a conflict log table, the existing
1549 * conflict log table associated with the subscription is removed via internal
1550 * dependency cleanup to prevent orphaned relations.
1551 *
1552 * On success, *conflicttablerelid is set to the OID of the conflict log table
1553 * that was created or validated, or to InvalidOid if no table is required.
1554 *
1555 * Returns true if the subscription's conflict log table reference must be
1556 * updated as a result of the destination change; false otherwise.
1557 */
1558static bool
1562{
1563 bool want_table;
1564 bool has_oldtable;
1565 bool update_relid = false;
1566 Oid relid = InvalidOid;
1567
1570
1571 if (has_oldtable)
1572 {
1573 /* There is a conflict log table already. */
1574 if (!want_table)
1575 {
1577 sub->conflictlogrelid);
1578 update_relid = true;
1579 }
1580 }
1581 else
1582 {
1583 /* There was no previous conflict log table. */
1584 if (want_table)
1585 {
1588
1589 relid = create_conflict_log_table(sub->oid, sub->name, sub->owner);
1590 update_relid = true;
1591
1592 /*
1593 * Establish an internal dependency between the conflict log table
1594 * and the subscription. For details refer comments in
1595 * CreateSubscription function.
1596 */
1600 }
1601 }
1602
1603 *conflicttablerelid = relid;
1604 return update_relid;
1605}
1606
1607/*
1608 * Alter the existing subscription.
1609 */
1612 bool isTopLevel)
1613{
1614 Relation rel;
1616 bool nulls[Natts_pg_subscription];
1619 HeapTuple tup;
1620 Oid subid;
1621 bool orig_conninfo_needed = true;
1622 bool update_tuple = false;
1623 bool update_failover = false;
1624 bool update_two_phase = false;
1625 bool check_pub_rdt = false;
1626 bool retain_dead_tuples;
1627 int max_retention;
1628 bool retention_active;
1629 char *new_conninfo = NULL;
1630 char *origin;
1631 Subscription *sub;
1634 SubOpts opts = {0};
1635
1637
1638 /* Fetch the existing tuple. */
1640 CStringGetDatum(stmt->subname));
1641
1642 if (!HeapTupleIsValid(tup))
1643 ereport(ERROR,
1645 errmsg("subscription \"%s\" does not exist",
1646 stmt->subname)));
1647
1649 subid = form->oid;
1650
1651 /* must be owner */
1654 stmt->subname);
1655
1656 /* parse and check options */
1657 switch (stmt->kind)
1658 {
1671 break;
1672
1675 break;
1676
1679 break;
1680
1684 break;
1685
1688 break;
1689
1692 break;
1693
1694 default:
1695 supported_opts = 0;
1696 break;
1697 }
1698
1699 if (supported_opts > 0)
1701
1702 /*
1703 * Ensure that ALTER SUBSCRIPTION commands that could be used to fix a
1704 * broken connection or prepare to drop a broken subscription don't
1705 * attempt to construct the conninfo. Otherwise, we might encounter the
1706 * error the user is trying to fix.
1707 *
1708 * Specifically, ALTER SUBSCRIPTION DISABLE, ALTER SUBSCRIPTION SERVER,
1709 * ALTER SUBSCRIPTION CONNECTION, or ALTER SUBSCRIPTION SET
1710 * (slot_name=NONE).
1711 *
1712 * NB: if the user specifies multiple SET options, then we may still need
1713 * to construct conninfo even if slot_name is set to NONE.
1714 */
1715 if (stmt->kind == ALTER_SUBSCRIPTION_ENABLED)
1716 {
1717 if (opts.specified_opts == SUBOPT_ENABLED && !opts.enabled)
1718 orig_conninfo_needed = false;
1719 }
1720 else if (stmt->kind == ALTER_SUBSCRIPTION_SERVER ||
1722 {
1723 orig_conninfo_needed = false;
1724 }
1725 else if (stmt->kind == ALTER_SUBSCRIPTION_OPTIONS)
1726 {
1727 /* ... SET (slot_name = NONE) with no other options */
1728 if (opts.specified_opts == SUBOPT_SLOT_NAME && !opts.slot_name)
1729 orig_conninfo_needed = false;
1730 }
1731
1732 /*
1733 * Skip ACL checks on the subscription's foreign server, if any. If
1734 * changing the server (or replacing it with a raw connection), then the
1735 * old one will be removed anyway. If changing something unrelated,
1736 * there's no need to do an additional ACL check here; that will be done
1737 * by the subscription worker.
1738 */
1739 sub = GetSubscription(subid, false, orig_conninfo_needed, false);
1740
1742 origin = sub->origin;
1745
1746 /*
1747 * Don't allow non-superuser modification of a subscription with
1748 * password_required=false.
1749 */
1750 if (!sub->passwordrequired && !superuser())
1751 ereport(ERROR,
1753 errmsg("password_required=false is superuser-only"),
1754 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1755
1756 /* Lock the subscription so nobody else can do anything with it. */
1758
1759 /* Form a new tuple. */
1760 memset(values, 0, sizeof(values));
1761 memset(nulls, false, sizeof(nulls));
1762 memset(replaces, false, sizeof(replaces));
1763
1765
1766 switch (stmt->kind)
1767 {
1769 {
1770 if (IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1771 {
1772 /*
1773 * The subscription must be disabled to allow slot_name as
1774 * 'none', otherwise, the apply worker will repeatedly try
1775 * to stream the data using that slot_name which neither
1776 * exists on the publisher nor the user will be allowed to
1777 * create it.
1778 */
1779 if (sub->enabled && !opts.slot_name)
1780 ereport(ERROR,
1782 errmsg("cannot set %s for enabled subscription",
1783 "slot_name = NONE")));
1784
1785 if (opts.slot_name)
1788 else
1789 nulls[Anum_pg_subscription_subslotname - 1] = true;
1791 }
1792
1793 if (opts.synchronous_commit)
1794 {
1796 CStringGetTextDatum(opts.synchronous_commit);
1798 }
1799
1800 if (IsSet(opts.specified_opts, SUBOPT_BINARY))
1801 {
1803 BoolGetDatum(opts.binary);
1805 }
1806
1807 if (IsSet(opts.specified_opts, SUBOPT_STREAMING))
1808 {
1810 CharGetDatum(opts.streaming);
1812 }
1813
1814 if (IsSet(opts.specified_opts, SUBOPT_DISABLE_ON_ERR))
1815 {
1817 = BoolGetDatum(opts.disableonerr);
1819 = true;
1820 }
1821
1822 if (IsSet(opts.specified_opts, SUBOPT_PASSWORD_REQUIRED))
1823 {
1824 /* Non-superuser may not disable password_required. */
1825 if (!opts.passwordrequired && !superuser())
1826 ereport(ERROR,
1828 errmsg("password_required=false is superuser-only"),
1829 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
1830
1832 = BoolGetDatum(opts.passwordrequired);
1834 = true;
1835 }
1836
1837 if (IsSet(opts.specified_opts, SUBOPT_RUN_AS_OWNER))
1838 {
1840 BoolGetDatum(opts.runasowner);
1842 }
1843
1844 if (IsSet(opts.specified_opts, SUBOPT_TWOPHASE_COMMIT))
1845 {
1846 /*
1847 * We need to update both the slot and the subscription
1848 * for the two_phase option. We can enable the two_phase
1849 * option for a slot only once the initial data
1850 * synchronization is done. This is to avoid missing some
1851 * data as explained in comments atop worker.c.
1852 */
1853 update_two_phase = !opts.twophase;
1854
1855 CheckAlterSubOption(sub, "two_phase", update_two_phase,
1856 isTopLevel);
1857
1858 /*
1859 * Modifying the two_phase slot option requires a slot
1860 * lookup by slot name, so changing the slot name at the
1861 * same time is not allowed.
1862 */
1863 if (update_two_phase &&
1864 IsSet(opts.specified_opts, SUBOPT_SLOT_NAME))
1865 ereport(ERROR,
1867 errmsg("\"slot_name\" and \"two_phase\" cannot be altered at the same time")));
1868
1869 /*
1870 * Note that workers may still survive even if the
1871 * subscription has been disabled.
1872 *
1873 * Ensure workers have already been exited to avoid
1874 * getting prepared transactions while we are disabling
1875 * the two_phase option. Otherwise, the changes of an
1876 * already prepared transaction can be replicated again
1877 * along with its corresponding commit, leading to
1878 * duplicate data or errors.
1879 */
1880 if (logicalrep_workers_find(subid, true, true))
1881 ereport(ERROR,
1883 errmsg("cannot alter \"two_phase\" when logical replication worker is still running"),
1884 errhint("Try again after some time.")));
1885
1886 /*
1887 * two_phase cannot be disabled if there are any
1888 * uncommitted prepared transactions present otherwise it
1889 * can lead to duplicate data or errors as explained in
1890 * the comment above.
1891 */
1892 if (update_two_phase &&
1894 LookupGXactBySubid(subid))
1895 ereport(ERROR,
1897 errmsg("cannot disable \"two_phase\" when prepared transactions exist"),
1898 errhint("Resolve these transactions and try again.")));
1899
1900 /* Change system catalog accordingly */
1902 CharGetDatum(opts.twophase ?
1906 }
1907
1908 if (IsSet(opts.specified_opts, SUBOPT_FAILOVER))
1909 {
1910 /*
1911 * Similar to the two_phase case above, we need to update
1912 * the failover option for both the slot and the
1913 * subscription.
1914 */
1915 update_failover = true;
1916
1917 CheckAlterSubOption(sub, "failover", update_failover,
1918 isTopLevel);
1919
1921 BoolGetDatum(opts.failover);
1923 }
1924
1925 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES))
1926 {
1928 BoolGetDatum(opts.retaindeadtuples);
1930
1931 /*
1932 * Update the retention status only if there's a change in
1933 * the retain_dead_tuples option value.
1934 *
1935 * Automatically marking retention as active when
1936 * retain_dead_tuples is enabled may not always be ideal,
1937 * especially if retention was previously stopped and the
1938 * user toggles retain_dead_tuples without adjusting the
1939 * publisher workload. However, this behavior provides a
1940 * convenient way for users to manually refresh the
1941 * retention status. Since retention will be stopped again
1942 * unless the publisher workload is reduced, this approach
1943 * is acceptable for now.
1944 */
1945 if (opts.retaindeadtuples != sub->retaindeadtuples)
1946 {
1948 BoolGetDatum(opts.retaindeadtuples);
1950
1951 retention_active = opts.retaindeadtuples;
1952 }
1953
1954 CheckAlterSubOption(sub, "retain_dead_tuples", false, isTopLevel);
1955
1956 /*
1957 * Workers may continue running even after the
1958 * subscription has been disabled.
1959 *
1960 * To prevent race conditions (as described in
1961 * CheckAlterSubOption()), ensure that all worker
1962 * processes have already exited before proceeding.
1963 */
1964 if (logicalrep_workers_find(subid, true, true))
1965 ereport(ERROR,
1967 errmsg("cannot alter retain_dead_tuples when logical replication worker is still running"),
1968 errhint("Try again after some time.")));
1969
1970 /*
1971 * Notify the launcher to manage the replication slot for
1972 * conflict detection. This ensures that replication slot
1973 * is efficiently handled (created, updated, or dropped)
1974 * in response to any configuration changes.
1975 */
1977
1978 check_pub_rdt = opts.retaindeadtuples;
1979 retain_dead_tuples = opts.retaindeadtuples;
1980 }
1981
1982 if (IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
1983 {
1985 Int32GetDatum(opts.maxretention);
1987
1988 max_retention = opts.maxretention;
1989 }
1990
1991 /*
1992 * Ensure that system configuration parameters are set
1993 * appropriately to support retain_dead_tuples and
1994 * max_retention_duration.
1995 */
1996 if (IsSet(opts.specified_opts, SUBOPT_RETAIN_DEAD_TUPLES) ||
1997 IsSet(opts.specified_opts, SUBOPT_MAX_RETENTION_DURATION))
2001 (max_retention > 0));
2002
2003 if (IsSet(opts.specified_opts, SUBOPT_ORIGIN))
2004 {
2006 CStringGetTextDatum(opts.origin);
2008
2009 /*
2010 * Check if changes from different origins may be received
2011 * from the publisher when the origin is changed to ANY
2012 * and retain_dead_tuples is enabled. Use |= so that we
2013 * don't clear the flag already set when
2014 * retain_dead_tuples was changed in the same command.
2015 */
2018
2019 origin = opts.origin;
2020 }
2021
2022 if (IsSet(opts.specified_opts, SUBOPT_WAL_RECEIVER_TIMEOUT))
2023 {
2025 CStringGetTextDatum(opts.wal_receiver_timeout);
2027 }
2028
2029 if (IsSet(opts.specified_opts, SUBOPT_CONFLICT_LOG_DEST))
2030 {
2033
2034 if (opts.conflictlogdest != old_dest)
2035 {
2036 bool update_relid;
2037 Oid relid = InvalidOid;
2038
2042
2044 old_dest,
2045 opts.conflictlogdest,
2046 &relid);
2047 if (update_relid)
2048 {
2050 ObjectIdGetDatum(relid);
2052 true;
2053 }
2054 }
2055 }
2056
2057 update_tuple = true;
2058 break;
2059 }
2060
2062 {
2063 Assert(IsSet(opts.specified_opts, SUBOPT_ENABLED));
2064
2065 if (!sub->slotname && opts.enabled)
2066 ereport(ERROR,
2068 errmsg("cannot enable subscription that does not have a slot name")));
2069
2070 /*
2071 * Check track_commit_timestamp only when enabling the
2072 * subscription in case it was disabled after creation. See
2073 * comments atop CheckSubDeadTupleRetention() for details.
2074 */
2075 CheckSubDeadTupleRetention(opts.enabled, !opts.enabled,
2077 sub->retentionactive, false);
2078
2080 BoolGetDatum(opts.enabled);
2082
2083 if (opts.enabled)
2085
2086 update_tuple = true;
2087
2088 /*
2089 * The subscription might be initially created with
2090 * connect=false and retain_dead_tuples=true, meaning the
2091 * remote server's status may not be checked. Ensure this
2092 * check is conducted now.
2093 */
2094 check_pub_rdt = sub->retaindeadtuples && opts.enabled;
2095 break;
2096 }
2097
2099 {
2103
2104 /*
2105 * Remove what was there before, either another foreign server
2106 * or a connection string.
2107 */
2108 if (form->subserver)
2109 {
2112 ForeignServerRelationId, form->subserver);
2113 }
2114 else
2115 {
2116 nulls[Anum_pg_subscription_subconninfo - 1] = true;
2118 }
2119
2120 /*
2121 * Check that the subscription owner has USAGE privileges on
2122 * the server.
2123 */
2124 new_server = GetForeignServerByName(stmt->servername, false);
2126 new_server->serverid,
2127 form->subowner, ACL_USAGE);
2128 if (aclresult != ACLCHECK_OK)
2129 ereport(ERROR,
2131 errmsg("subscription owner \"%s\" does not have permission on foreign server \"%s\"",
2132 GetUserNameFromId(form->subowner, false),
2133 new_server->servername));
2134
2135 /* make sure a user mapping exists */
2136 GetUserMapping(form->subowner, new_server->serverid);
2137
2139 new_server);
2140
2141 /* Load the library providing us libpq calls. */
2142 load_file("libpqwalreceiver", false);
2143 /* Check the connection info string. */
2145 sub->passwordrequired && !sub->ownersuperuser);
2146
2149
2152
2153 update_tuple = true;
2154 }
2155
2156 /*
2157 * Since the remote server configuration might have changed,
2158 * perform a check to ensure it permits enabling
2159 * retain_dead_tuples.
2160 */
2162 break;
2163
2165 /* remove reference to foreign server and dependencies, if present */
2166 if (form->subserver)
2167 {
2170 ForeignServerRelationId, form->subserver);
2171
2174 }
2175
2176 new_conninfo = stmt->conninfo;
2177
2178 /* Load the library providing us libpq calls. */
2179 load_file("libpqwalreceiver", false);
2180 /* Check the connection info string. */
2182 sub->passwordrequired && !sub->ownersuperuser);
2183
2185 CStringGetTextDatum(stmt->conninfo);
2187 update_tuple = true;
2188
2189 /*
2190 * Since the remote server configuration might have changed,
2191 * perform a check to ensure it permits enabling
2192 * retain_dead_tuples.
2193 */
2195 break;
2196
2198 {
2200 publicationListToArray(stmt->publication);
2202
2203 update_tuple = true;
2204
2205 /* Refresh if user asked us to. */
2206 if (opts.refresh)
2207 {
2208 if (!sub->enabled)
2209 ereport(ERROR,
2211 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
2212 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false).")));
2213
2214 /*
2215 * See ALTER_SUBSCRIPTION_REFRESH_PUBLICATION for details
2216 * why this is not allowed.
2217 */
2218 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2219 ereport(ERROR,
2221 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
2222 errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
2223
2224 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
2225
2226 /* Make sure refresh sees the new list of publications. */
2227 sub->publications = stmt->publication;
2228
2229 AlterSubscription_refresh(sub, opts.copy_data,
2230 stmt->publication);
2231 }
2232
2233 break;
2234 }
2235
2238 {
2239 List *publist;
2241
2242 publist = merge_publications(sub->publications, stmt->publication, isadd, stmt->subname);
2246
2247 update_tuple = true;
2248
2249 /* Refresh if user asked us to. */
2250 if (opts.refresh)
2251 {
2252 /* We only need to validate user specified publications. */
2253 List *validate_publications = (isadd) ? stmt->publication : NULL;
2254
2255 if (!sub->enabled)
2256 ereport(ERROR,
2258 errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"),
2259 /* translator: %s is an SQL ALTER command */
2260 errhint("Use %s instead.",
2261 isadd ?
2262 "ALTER SUBSCRIPTION ... ADD PUBLICATION ... WITH (refresh = false)" :
2263 "ALTER SUBSCRIPTION ... DROP PUBLICATION ... WITH (refresh = false)")));
2264
2265 /*
2266 * See ALTER_SUBSCRIPTION_REFRESH_PUBLICATION for details
2267 * why this is not allowed.
2268 */
2269 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2270 ereport(ERROR,
2272 errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"),
2273 /* translator: %s is an SQL ALTER command */
2274 errhint("Use %s with refresh = false, or with copy_data = false, or use DROP/CREATE SUBSCRIPTION.",
2275 isadd ?
2276 "ALTER SUBSCRIPTION ... ADD PUBLICATION" :
2277 "ALTER SUBSCRIPTION ... DROP PUBLICATION")));
2278
2279 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh");
2280
2281 /* Refresh the new list of publications. */
2282 sub->publications = publist;
2283
2284 AlterSubscription_refresh(sub, opts.copy_data,
2286 }
2287
2288 break;
2289 }
2290
2292 {
2293 if (!sub->enabled)
2294 ereport(ERROR,
2296 errmsg("%s is not allowed for disabled subscriptions",
2297 "ALTER SUBSCRIPTION ... REFRESH PUBLICATION")));
2298
2299 /*
2300 * The subscription option "two_phase" requires that
2301 * replication has passed the initial table synchronization
2302 * phase before the two_phase becomes properly enabled.
2303 *
2304 * But, having reached this two-phase commit "enabled" state
2305 * we must not allow any subsequent table initialization to
2306 * occur. So the ALTER SUBSCRIPTION ... REFRESH PUBLICATION is
2307 * disallowed when the user had requested two_phase = on mode.
2308 *
2309 * The exception to this restriction is when copy_data =
2310 * false, because when copy_data is false the tablesync will
2311 * start already in READY state and will exit directly without
2312 * doing anything.
2313 *
2314 * For more details see comments atop worker.c.
2315 */
2316 if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data)
2317 ereport(ERROR,
2319 errmsg("ALTER SUBSCRIPTION ... REFRESH PUBLICATION with copy_data is not allowed when two_phase is enabled"),
2320 errhint("Use ALTER SUBSCRIPTION ... REFRESH PUBLICATION with copy_data = false, or use DROP/CREATE SUBSCRIPTION.")));
2321
2322 PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH PUBLICATION");
2323
2324 AlterSubscription_refresh(sub, opts.copy_data, NULL);
2325
2326 break;
2327 }
2328
2330 {
2331 if (!sub->enabled)
2332 ereport(ERROR,
2334 errmsg("%s is not allowed for disabled subscriptions",
2335 "ALTER SUBSCRIPTION ... REFRESH SEQUENCES"));
2336
2338
2339 break;
2340 }
2341
2343 {
2344 /* ALTER SUBSCRIPTION ... SKIP supports only LSN option */
2345 Assert(IsSet(opts.specified_opts, SUBOPT_LSN));
2346
2347 /*
2348 * If the user sets subskiplsn, we do a sanity check to make
2349 * sure that the specified LSN is a probable value.
2350 */
2351 if (XLogRecPtrIsValid(opts.lsn))
2352 {
2354 char originname[NAMEDATALEN];
2355 XLogRecPtr remote_lsn;
2356
2358 originname, sizeof(originname));
2360 remote_lsn = replorigin_get_progress(originid, false);
2361
2362 /* Check the given LSN is at least a future LSN */
2363 if (XLogRecPtrIsValid(remote_lsn) && opts.lsn < remote_lsn)
2364 ereport(ERROR,
2366 errmsg("skip WAL location (LSN %X/%08X) must be greater than origin LSN %X/%08X",
2367 LSN_FORMAT_ARGS(opts.lsn),
2368 LSN_FORMAT_ARGS(remote_lsn))));
2369 }
2370
2373
2374 update_tuple = true;
2375 break;
2376 }
2377
2378 default:
2379 elog(ERROR, "unrecognized ALTER SUBSCRIPTION kind %d",
2380 stmt->kind);
2381 }
2382
2383 /* Update the catalog if needed. */
2384 if (update_tuple)
2385 {
2387 replaces);
2388
2389 CatalogTupleUpdate(rel, &tup->t_self, tup);
2390
2392 }
2393
2394 /*
2395 * Try to acquire the connection necessary either for modifying the slot
2396 * or for checking if the remote server permits enabling
2397 * retain_dead_tuples.
2398 *
2399 * This has to be at the end because otherwise if there is an error while
2400 * doing the database operations we won't be able to rollback altered
2401 * slot.
2402 */
2404 {
2405 bool must_use_password;
2406 char *err;
2408
2410
2411 /* Load the library providing us libpq calls. */
2412 load_file("libpqwalreceiver", false);
2413
2414 /*
2415 * Try to connect to the publisher, using the new connection string if
2416 * available.
2417 */
2420 true, true, must_use_password, sub->name,
2421 &err);
2422 if (!wrconn)
2423 ereport(ERROR,
2425 errmsg("subscription \"%s\" could not connect to the publisher: %s",
2426 sub->name, err)));
2427
2428 PG_TRY();
2429 {
2432
2434 retain_dead_tuples, origin, NULL, 0,
2435 sub->name);
2436
2439 update_failover ? &opts.failover : NULL,
2440 update_two_phase ? &opts.twophase : NULL);
2441 }
2442 PG_FINALLY();
2443 {
2445 }
2446 PG_END_TRY();
2447 }
2448
2450
2452
2453 /* Wake up related replication workers to handle this change quickly. */
2455
2456 return myself;
2457}
2458
2459/*
2460 * Construct conninfo from a subscription's server. Like libpqrcv_connect(),
2461 * if an error occurs, set *err to the error message and return NULL.
2462 *
2463 * However, failures in ForeignServerConnectionString() may ereport(ERROR),
2464 * and (also like libpqrcv_connect) it's not worth adding the machinery to
2465 * pass all of those back to the caller just to cover this one case.
2466 */
2467static char *
2469{
2471 ForeignServer *server;
2472
2473 *err = NULL;
2474
2475 server = GetForeignServer(subserver);
2476
2479 if (aclresult != ACLCHECK_OK)
2480 {
2481 /*
2482 * Unable to generate connection string because permissions on the
2483 * foreign server have been removed. Follow the same logic as an
2484 * unusable subconninfo (which will result in an ERROR later unless
2485 * slot_name = NONE).
2486 */
2487 *err = psprintf(_("subscription owner \"%s\" does not have permission on foreign server \"%s\""),
2489 server->servername);
2490 return NULL;
2491 }
2492
2494}
2495
2496/*
2497 * Drop subscription's conflict log table
2498 *
2499 * The conflict log table is registered as an internal dependency of the
2500 * subscription. This function removes the dependency by performing a
2501 * cascading deletion on the subscription object, which in turn drops the
2502 * associated conflict log table.
2503 *
2504 * This is used to clean up conflict log tables that are no longer required,
2505 * preventing accumulation of stale or orphaned relations.
2506 *
2507 * NOTE:
2508 * Only conflict log tables are currently managed via this internal dependency
2509 * mechanism.
2510 */
2511static void
2513{
2514 /* Drop any dependent conflict log table */
2516 {
2517 ObjectAddress object;
2518 char *conflictrelname;
2519
2521 if (conflictrelname == NULL)
2522 elog(ERROR, "cache lookup failed for relation %u",
2524
2525 /*
2526 * By using PERFORM_DELETION_SKIP_ORIGINAL, we ensure that only the
2527 * conflict log table is deleted while the subscription remains.
2528 */
2533
2535 errmsg("dropped conflict log table \"%s\" for subscription \"%s\"",
2537 subname));
2538 }
2539}
2540
2541/*
2542 * Drop a subscription
2543 */
2544void
2546{
2547 Relation rel;
2549 HeapTuple tup;
2550 Oid subid;
2551 Oid subowner;
2552 Oid subserver;
2554 char *subconninfo = NULL;
2555 Datum datum;
2556 bool isnull;
2557 char *subname;
2558 char *conninfo = NULL;
2559 char *slotname;
2561 ListCell *lc;
2562 char originname[NAMEDATALEN];
2563 char *err = NULL;
2566 List *rstates;
2567 bool must_use_password;
2568
2569 /*
2570 * The launcher may concurrently start a new worker for this subscription.
2571 * During initialization, the worker checks for subscription validity and
2572 * exits if the subscription has already been dropped. See
2573 * InitializeLogRepWorker.
2574 */
2576
2578 CStringGetDatum(stmt->subname));
2579
2580 if (!HeapTupleIsValid(tup))
2581 {
2582 table_close(rel, NoLock);
2583
2584 if (!stmt->missing_ok)
2585 ereport(ERROR,
2587 errmsg("subscription \"%s\" does not exist",
2588 stmt->subname)));
2589 else
2591 (errmsg("subscription \"%s\" does not exist, skipping",
2592 stmt->subname)));
2593
2594 return;
2595 }
2596
2599 if (!isnull)
2600 subconninfo = TextDatumGetCString(datum);
2601
2603 subid = form->oid;
2604 subowner = form->subowner;
2605 subserver = form->subserver;
2606 subconflictlogrelid = form->subconflictlogrelid;
2607 must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
2608
2609 /* must be owner */
2612 stmt->subname);
2613
2614 /* DROP hook for the subscription being removed */
2616
2617 /*
2618 * Lock the subscription so nobody else can do anything with it (including
2619 * the replication workers).
2620 */
2622
2623 /* Get subname */
2626 subname = pstrdup(NameStr(*DatumGetName(datum)));
2627
2628 /* Get slotname */
2631 if (!isnull)
2632 slotname = pstrdup(NameStr(*DatumGetName(datum)));
2633 else
2634 slotname = NULL;
2635
2636 /*
2637 * Since dropping a replication slot is not transactional, the replication
2638 * slot stays dropped even if the transaction rolls back. So we cannot
2639 * run DROP SUBSCRIPTION inside a transaction block if dropping the
2640 * replication slot. Also, in this case, we report a message for dropping
2641 * the subscription to the cumulative stats system.
2642 *
2643 * XXX The command name should really be something like "DROP SUBSCRIPTION
2644 * of a subscription that is associated with a replication slot", but we
2645 * don't have the proper facilities for that.
2646 */
2647 if (slotname)
2648 PreventInTransactionBlock(isTopLevel, "DROP SUBSCRIPTION");
2649
2652
2653 /* Remove the tuple from catalog. */
2654 CatalogTupleDelete(rel, &tup->t_self);
2655
2657
2658 /*
2659 * Stop all the subscription workers immediately.
2660 *
2661 * This is necessary if we are dropping the replication slot, so that the
2662 * slot becomes accessible.
2663 *
2664 * It is also necessary if the subscription is disabled and was disabled
2665 * in the same transaction. Then the workers haven't seen the disabling
2666 * yet and will still be running, leading to hangs later when we want to
2667 * drop the replication origin. If the subscription was disabled before
2668 * this transaction, then there shouldn't be any workers left, so this
2669 * won't make a difference.
2670 *
2671 * New workers won't be started because we hold an exclusive lock on the
2672 * subscription till the end of the transaction.
2673 */
2674 subworkers = logicalrep_workers_find(subid, false, true);
2675 foreach(lc, subworkers)
2676 {
2678
2680 }
2682
2683 /*
2684 * Remove the no-longer-useful entry in the launcher's table of apply
2685 * worker start times.
2686 *
2687 * If this transaction rolls back, the launcher might restart a failed
2688 * apply worker before wal_retrieve_retry_interval milliseconds have
2689 * elapsed, but that's pretty harmless.
2690 */
2692
2693 /*
2694 * Cleanup of tablesync replication origins.
2695 *
2696 * Any READY-state relations would already have dealt with clean-ups.
2697 *
2698 * Note that the state can't change because we have already stopped both
2699 * the apply and tablesync workers and they can't restart because of
2700 * exclusive lock on the subscription.
2701 */
2702 rstates = GetSubscriptionRelations(subid, true, false, true);
2703 foreach(lc, rstates)
2704 {
2706 Oid relid = rstate->relid;
2707
2708 /* Only cleanup resources of tablesync workers */
2709 if (!OidIsValid(relid))
2710 continue;
2711
2712 /*
2713 * Drop the tablesync's origin tracking if exists.
2714 *
2715 * It is possible that the origin is not yet created for tablesync
2716 * worker so passing missing_ok = true. This can happen for the states
2717 * before SUBREL_STATE_DATASYNC.
2718 */
2720 sizeof(originname));
2721 replorigin_drop_by_name(originname, true, false);
2722 }
2723
2724 /* Drop subscription's conflict log table */
2726
2727 /* Clean up dependencies */
2730
2731 /* Remove any associated relation synchronization states. */
2733
2734 /* Remove the origin tracking if exists. */
2736 replorigin_drop_by_name(originname, true, false);
2737
2738 /*
2739 * Tell the cumulative stats system that the subscription is getting
2740 * dropped.
2741 */
2743
2744 /*
2745 * If there is no slot associated with the subscription, we can finish
2746 * here.
2747 */
2748 if (!slotname && rstates == NIL)
2749 {
2750 table_close(rel, NoLock);
2751 return;
2752 }
2753
2754 /*
2755 * Try to acquire the connection necessary for dropping slots.
2756 *
2757 * Note: If the slotname is NONE/NULL then we allow the command to finish
2758 * and users need to manually cleanup the apply and tablesync worker slots
2759 * later.
2760 *
2761 * This has to be at the end because otherwise if there is an error while
2762 * doing the database operations we won't be able to rollback dropped
2763 * slot.
2764 */
2765 load_file("libpqwalreceiver", false);
2766
2767 if (OidIsValid(subserver))
2769 else
2770 conninfo = subconninfo;
2771
2772 if (conninfo)
2773 wrconn = walrcv_connect(conninfo, true, true, must_use_password,
2774 subname, &err);
2775
2776 if (wrconn == NULL)
2777 {
2778 if (!slotname)
2779 {
2780 /* be tidy */
2782 table_close(rel, NoLock);
2783 return;
2784 }
2785 else
2786 {
2787 ReportSlotConnectionError(rstates, subid, slotname, err);
2788 }
2789 }
2790
2791 PG_TRY();
2792 {
2793 foreach(lc, rstates)
2794 {
2796 Oid relid = rstate->relid;
2797
2798 /* Only cleanup resources of tablesync workers */
2799 if (!OidIsValid(relid))
2800 continue;
2801
2802 /*
2803 * Drop the tablesync slots associated with removed tables.
2804 *
2805 * For SYNCDONE/READY states, the tablesync slot is known to have
2806 * already been dropped by the tablesync worker.
2807 *
2808 * For other states, there is no certainty, maybe the slot does
2809 * not exist yet. Also, if we fail after removing some of the
2810 * slots, next time, it will again try to drop already dropped
2811 * slots and fail. For these reasons, we allow missing_ok = true
2812 * for the drop.
2813 */
2814 if (rstate->state != SUBREL_STATE_SYNCDONE)
2815 {
2816 char syncslotname[NAMEDATALEN] = {0};
2817
2819 sizeof(syncslotname));
2821 }
2822 }
2823
2825
2826 /*
2827 * If there is a slot associated with the subscription, then drop the
2828 * replication slot at the publisher.
2829 */
2830 if (slotname)
2831 ReplicationSlotDropAtPubNode(wrconn, slotname, false);
2832 }
2833 PG_FINALLY();
2834 {
2836 }
2837 PG_END_TRY();
2838
2839 table_close(rel, NoLock);
2840}
2841
2842/*
2843 * Drop the replication slot at the publisher node using the replication
2844 * connection.
2845 *
2846 * missing_ok - if true then only issue a LOG message if the slot doesn't
2847 * exist.
2848 */
2849void
2850ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok)
2851{
2852 StringInfoData cmd;
2853
2854 Assert(wrconn);
2855
2856 load_file("libpqwalreceiver", false);
2857
2858 initStringInfo(&cmd);
2859 appendStringInfoString(&cmd, "DROP_REPLICATION_SLOT ");
2860 appendQuotedIdentifier(&cmd, slotname);
2861 appendStringInfoString(&cmd, " WAIT");
2862
2863 PG_TRY();
2864 {
2865 WalRcvExecResult *res;
2866
2867 res = walrcv_exec(wrconn, cmd.data, 0, NULL);
2868
2869 if (res->status == WALRCV_OK_COMMAND)
2870 {
2871 /* NOTICE. Success. */
2873 (errmsg("dropped replication slot \"%s\" on publisher",
2874 slotname)));
2875 }
2876 else if (res->status == WALRCV_ERROR &&
2877 missing_ok &&
2879 {
2880 /* LOG. Error, but missing_ok = true. */
2881 ereport(LOG,
2882 (errmsg("could not drop replication slot \"%s\" on publisher: %s",
2883 slotname, res->err)));
2884 }
2885 else
2886 {
2887 /* ERROR. */
2888 ereport(ERROR,
2890 errmsg("could not drop replication slot \"%s\" on publisher: %s",
2891 slotname, res->err)));
2892 }
2893
2895 }
2896 PG_FINALLY();
2897 {
2898 pfree(cmd.data);
2899 }
2900 PG_END_TRY();
2901}
2902
2903/*
2904 * Internal workhorse for changing a subscription owner
2905 */
2906static void
2908{
2911
2913
2914 /* Must only alter subscriptions belonging to the current database. */
2915 Assert(form->subdbid == MyDatabaseId);
2916
2917 if (form->subowner == newOwnerId)
2918 return;
2919
2922 NameStr(form->subname));
2923
2924 /*
2925 * Don't allow non-superuser modification of a subscription with
2926 * password_required=false.
2927 */
2928 if (!form->subpasswordrequired && !superuser())
2929 ereport(ERROR,
2931 errmsg("password_required=false is superuser-only"),
2932 errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser.")));
2933
2934 /* Must be able to become new owner */
2936
2937 /*
2938 * current owner must have CREATE on database
2939 *
2940 * This is consistent with how ALTER SCHEMA ... OWNER TO works, but some
2941 * other object types behave differently (e.g. you can't give a table to a
2942 * user who lacks CREATE privileges on a schema).
2943 */
2946 if (aclresult != ACLCHECK_OK)
2949
2950 /*
2951 * If the subscription uses a server, check that the new owner has USAGE
2952 * privileges on the server, that a user mapping exists, and that the
2953 * resulting connection string is valid for the new owner.
2954 */
2955 if (OidIsValid(form->subserver))
2956 {
2957 char *conninfo;
2958 ForeignServer *server = GetForeignServer(form->subserver);
2959
2961 if (aclresult != ACLCHECK_OK)
2962 ereport(ERROR,
2964 errmsg("new subscription owner \"%s\" does not have permission on foreign server \"%s\"",
2966 server->servername));
2967
2968 /* make sure a user mapping exists */
2970
2971 conninfo = ForeignServerConnectionString(newOwnerId, server);
2972
2973 /* Load the library providing us libpq calls. */
2974 load_file("libpqwalreceiver", false);
2975 /* Check the connection info string. */
2976 walrcv_check_conninfo(conninfo,
2977 form->subpasswordrequired &&
2979 }
2980
2981 form->subowner = newOwnerId;
2982 CatalogTupleUpdate(rel, &tup->t_self, tup);
2983
2984 /* Update owner of the conflict log table if it exists. */
2985 if (OidIsValid(form->subconflictlogrelid))
2986 ATExecChangeOwner(form->subconflictlogrelid, newOwnerId, true,
2988
2989 /* Update owner dependency reference */
2991 form->oid,
2992 newOwnerId);
2993
2995 form->oid, 0);
2996
2997 /* Wake up related background processes to handle this change quickly. */
3000}
3001
3002/*
3003 * Change subscription owner -- by name
3004 */
3007{
3008 Oid subid;
3009 HeapTuple tup;
3010 Relation rel;
3011 ObjectAddress address;
3013
3015
3018
3019 if (!HeapTupleIsValid(tup))
3020 ereport(ERROR,
3022 errmsg("subscription \"%s\" does not exist", name)));
3023
3025 subid = form->oid;
3026
3028
3030
3032
3034
3035 return address;
3036}
3037
3038/*
3039 * Change subscription owner -- by OID
3040 */
3041void
3043{
3044 HeapTuple tup;
3045 Relation rel;
3047
3049
3051
3052 if (!HeapTupleIsValid(tup))
3053 ereport(ERROR,
3055 errmsg("subscription with OID %u does not exist", subid)));
3056
3058
3059 /*
3060 * Don't process subscriptions belonging to other databases. While
3061 * pg_subscription is a shared catalog, subscriptions refer to db-local
3062 * objects which exist only in the database identified by subdbid.
3063 */
3064 if (form->subdbid == MyDatabaseId)
3066
3068
3070}
3071
3072/*
3073 * Check and log a warning if the publisher has subscribed to the same table,
3074 * its partition ancestors (if it's a partition), or its partition children (if
3075 * it's a partitioned table), from some other publishers. This check is
3076 * required in the following scenarios:
3077 *
3078 * 1) For CREATE SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH PUBLICATION
3079 * statements with "copy_data = true" and "origin = none":
3080 * - Warn the user that data with an origin might have been copied.
3081 * - This check is skipped for tables already added, as incremental sync via
3082 * WAL allows origin tracking. The list of such tables is in
3083 * subrel_local_oids.
3084 *
3085 * 2) For CREATE SUBSCRIPTION and ALTER SUBSCRIPTION ... REFRESH PUBLICATION
3086 * statements with "retain_dead_tuples = true" and "origin = any", and for
3087 * ALTER SUBSCRIPTION statements that modify retain_dead_tuples or origin,
3088 * or when the publisher's status changes (e.g., due to a connection string
3089 * update):
3090 * - Warn the user that only conflict detection info for local changes on
3091 * the publisher is retained. Data from other origins may lack sufficient
3092 * details for reliable conflict detection.
3093 * - See comments atop worker.c for more details.
3094 */
3095static void
3097 bool copydata, bool retain_dead_tuples,
3098 char *origin, Oid *subrel_local_oids,
3099 int subrel_count, char *subname)
3100{
3101 WalRcvExecResult *res;
3102 StringInfoData cmd;
3103 TupleTableSlot *slot;
3104 Oid tableRow[1] = {TEXTOID};
3105 List *publist = NIL;
3106 int i;
3107 bool check_rdt;
3108 bool check_table_sync;
3109 bool origin_none = origin &&
3111
3112 /*
3113 * Enable retain_dead_tuples checks only when origin is set to 'any',
3114 * since with origin='none' only local changes are replicated to the
3115 * subscriber.
3116 */
3118
3119 /*
3120 * Enable table synchronization checks only when origin is 'none', to
3121 * ensure that data from other origins is not inadvertently copied.
3122 */
3124
3125 /* retain_dead_tuples and table sync checks occur separately */
3127
3128 /* Return if no checks are required */
3129 if (!check_rdt && !check_table_sync)
3130 return;
3131
3132 initStringInfo(&cmd);
3134 "SELECT DISTINCT P.pubname AS pubname\n"
3135 "FROM pg_publication P,\n"
3136 " LATERAL pg_get_publication_tables(P.pubname) GPT\n"
3137 " JOIN pg_subscription_rel PS ON (GPT.relid = PS.srrelid OR"
3138 " GPT.relid IN (SELECT relid FROM pg_partition_ancestors(PS.srrelid) UNION"
3139 " SELECT relid FROM pg_partition_tree(PS.srrelid))),\n"
3140 " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
3141 "WHERE C.oid = GPT.relid AND P.pubname IN (");
3142 GetPublicationsStr(publications, &cmd, true);
3143 appendStringInfoString(&cmd, ")\n");
3144
3145 /*
3146 * In case of ALTER SUBSCRIPTION ... REFRESH PUBLICATION,
3147 * subrel_local_oids contains the list of relation oids that are already
3148 * present on the subscriber. This check should be skipped for these
3149 * tables if checking for table sync scenario. However, when handling the
3150 * retain_dead_tuples scenario, ensure all tables are checked, as some
3151 * existing tables may now include changes from other origins due to newly
3152 * created subscriptions on the publisher.
3153 */
3154 if (check_table_sync)
3155 {
3156 for (i = 0; i < subrel_count; i++)
3157 {
3158 Oid relid = subrel_local_oids[i];
3159 char *schemaname = get_namespace_name(get_rel_namespace(relid));
3160 char *tablename = get_rel_name(relid);
3161 char *schemaname_lit = quote_literal_cstr(schemaname);
3162 char *tablename_lit = quote_literal_cstr(tablename);
3163
3164 appendStringInfo(&cmd, "AND NOT (N.nspname = %s AND C.relname = %s)\n",
3166
3169 }
3170 }
3171
3172 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
3173 pfree(cmd.data);
3174
3175 if (res->status != WALRCV_OK_TUPLES)
3176 ereport(ERROR,
3178 errmsg("could not receive list of replicated tables from the publisher: %s",
3179 res->err)));
3180
3181 /* Process publications. */
3183 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3184 {
3185 char *pubname;
3186 bool isnull;
3187
3188 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3189 Assert(!isnull);
3190
3191 ExecClearTuple(slot);
3193 }
3194
3195 /*
3196 * Log a warning if the publisher has subscribed to the same table from
3197 * some other publisher. We cannot know the origin of data during the
3198 * initial sync. Data origins can be found only from the WAL by looking at
3199 * the origin id.
3200 *
3201 * XXX: For simplicity, we don't check whether the table has any data or
3202 * not. If the table doesn't have any data then we don't need to
3203 * distinguish between data having origin and data not having origin so we
3204 * can avoid logging a warning for table sync scenario.
3205 */
3206 if (publist)
3207 {
3209
3210 /* Prepare the list of publication(s) for warning message. */
3213
3214 if (check_table_sync)
3217 errmsg("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin",
3218 subname),
3219 errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
3220 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
3222 errhint("Verify that initial data copied from the publisher tables did not come from other origins."));
3223 else
3226 errmsg("subscription \"%s\" enabled retain_dead_tuples but might not reliably detect conflicts for changes from different origins",
3227 subname),
3228 errdetail_plural("The subscription subscribes to a publication (%s) that contains tables that are written to by other subscriptions.",
3229 "The subscription subscribes to publications (%s) that contain tables that are written to by other subscriptions.",
3231 errhint("Consider using origin = NONE or disabling retain_dead_tuples."));
3232 }
3233
3235
3237}
3238
3239/*
3240 * This function is similar to check_publications_origin_tables and serves
3241 * same purpose for sequences.
3242 */
3243static void
3245 bool copydata, char *origin,
3247 char *subname)
3248{
3249 WalRcvExecResult *res;
3250 StringInfoData cmd;
3251 TupleTableSlot *slot;
3252 Oid tableRow[1] = {TEXTOID};
3253 List *publist = NIL;
3254
3255 /*
3256 * Enable sequence synchronization checks only when origin is 'none' , to
3257 * ensure that sequence data from other origins is not inadvertently
3258 * copied. This check is necessary if the publisher is running PG19 or
3259 * later, where logical replication sequence synchronization is supported.
3260 */
3261 if (!copydata || pg_strcasecmp(origin, LOGICALREP_ORIGIN_NONE) != 0 ||
3262 walrcv_server_version(wrconn) < 190000)
3263 return;
3264
3265 initStringInfo(&cmd);
3267 "SELECT DISTINCT P.pubname AS pubname\n"
3268 "FROM pg_publication P,\n"
3269 " LATERAL pg_get_publication_sequences(P.pubname) GPS\n"
3270 " JOIN pg_subscription_rel PS ON (GPS.relid = PS.srrelid),\n"
3271 " pg_class C JOIN pg_namespace N ON (N.oid = C.relnamespace)\n"
3272 "WHERE C.oid = GPS.relid AND P.pubname IN (");
3273
3274 GetPublicationsStr(publications, &cmd, true);
3275 appendStringInfoString(&cmd, ")\n");
3276
3277 /*
3278 * In case of ALTER SUBSCRIPTION ... REFRESH PUBLICATION,
3279 * subrel_local_oids contains the list of relations that are already
3280 * present on the subscriber. This check should be skipped as these will
3281 * not be re-synced.
3282 */
3283 for (int i = 0; i < subrel_count; i++)
3284 {
3285 Oid relid = subrel_local_oids[i];
3286 char *schemaname = get_namespace_name(get_rel_namespace(relid));
3287 char *seqname = get_rel_name(relid);
3288 char *schemaname_lit = quote_literal_cstr(schemaname);
3289 char *seqname_lit = quote_literal_cstr(seqname);
3290
3291 appendStringInfo(&cmd,
3292 "AND NOT (N.nspname = %s AND C.relname = %s)\n",
3294
3297 }
3298
3299 res = walrcv_exec(wrconn, cmd.data, 1, tableRow);
3300 pfree(cmd.data);
3301
3302 if (res->status != WALRCV_OK_TUPLES)
3303 ereport(ERROR,
3305 errmsg("could not receive list of replicated sequences from the publisher: %s",
3306 res->err)));
3307
3308 /* Process publications. */
3310 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3311 {
3312 char *pubname;
3313 bool isnull;
3314
3315 pubname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3316 Assert(!isnull);
3317
3318 ExecClearTuple(slot);
3320 }
3321
3322 /*
3323 * Log a warning if the publisher has subscribed to the same sequence from
3324 * some other publisher. We cannot know the origin of sequences data
3325 * during the initial sync.
3326 */
3327 if (publist)
3328 {
3330
3331 /* Prepare the list of publication(s) for warning message. */
3334
3337 errmsg("subscription \"%s\" requested copy_data with origin = NONE but might copy data that had a different origin",
3338 subname),
3339 errdetail_plural("The subscription subscribes to a publication (%s) that contains sequences that are written to by other subscriptions.",
3340 "The subscription subscribes to publications (%s) that contain sequences that are written to by other subscriptions.",
3342 errhint("Verify that initial data copied from the publisher sequences did not come from other origins."));
3343 }
3344
3346
3348}
3349
3350/*
3351 * Determine whether the retain_dead_tuples can be enabled based on the
3352 * publisher's status.
3353 *
3354 * This option is disallowed if the publisher is running a version earlier
3355 * than the PG19, or if the publisher is in recovery (i.e., it is a standby
3356 * server).
3357 *
3358 * See comments atop worker.c for a detailed explanation.
3359 */
3360static void
3362{
3363 WalRcvExecResult *res;
3364 Oid RecoveryRow[1] = {BOOLOID};
3365 TupleTableSlot *slot;
3366 bool isnull;
3367 bool remote_in_recovery;
3368
3369 if (walrcv_server_version(wrconn) < 190000)
3370 ereport(ERROR,
3372 errmsg("cannot enable retain_dead_tuples if the publisher is running a version earlier than PostgreSQL 19"));
3373
3374 res = walrcv_exec(wrconn, "SELECT pg_is_in_recovery()", 1, RecoveryRow);
3375
3376 if (res->status != WALRCV_OK_TUPLES)
3377 ereport(ERROR,
3379 errmsg("could not obtain recovery progress from the publisher: %s",
3380 res->err)));
3381
3383 if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3384 elog(ERROR, "failed to fetch tuple for the recovery progress");
3385
3386 remote_in_recovery = DatumGetBool(slot_getattr(slot, 1, &isnull));
3387
3389 ereport(ERROR,
3391 errmsg("cannot enable retain_dead_tuples if the publisher is in recovery"));
3392
3394
3396}
3397
3398/*
3399 * Check if the subscriber's configuration is adequate to enable the
3400 * retain_dead_tuples option.
3401 *
3402 * Issue an ERROR if the wal_level does not support the use of replication
3403 * slots when check_guc is set to true.
3404 *
3405 * Issue a WARNING if track_commit_timestamp is not enabled when check_guc is
3406 * set to true. This is only to highlight the importance of enabling
3407 * track_commit_timestamp instead of catching all the misconfigurations, as
3408 * this setting can be adjusted after subscription creation. Without it, the
3409 * apply worker will simply skip conflict detection.
3410 *
3411 * Issue a WARNING or NOTICE if the subscription is disabled and the retention
3412 * is active. Do not raise an ERROR since users can only modify
3413 * retain_dead_tuples for disabled subscriptions. And as long as the
3414 * subscription is enabled promptly, it will not pose issues.
3415 *
3416 * Issue a NOTICE to inform users that max_retention_duration is
3417 * ineffective when retain_dead_tuples is disabled for a subscription. An ERROR
3418 * is not issued because setting max_retention_duration causes no harm,
3419 * even when it is ineffective.
3420 */
3421void
3425 bool max_retention_set)
3426{
3429
3431 {
3433 ereport(ERROR,
3435 errmsg("\"wal_level\" is insufficient to create the replication slot required by retain_dead_tuples"),
3436 errhint("\"wal_level\" must be set to \"replica\" or \"logical\" at server start."));
3437
3441 errmsg("commit timestamp and origin data required for detecting conflicts won't be retained"),
3442 errhint("Consider setting \"%s\" to true.",
3443 "track_commit_timestamp"));
3444
3448 errmsg("deleted rows to detect conflicts would not be removed until the subscription is enabled"),
3450 ? errhint("Consider setting %s to false.",
3451 "retain_dead_tuples") : 0);
3452 }
3453 else if (max_retention_set)
3454 {
3457 errmsg("max_retention_duration is ineffective when retain_dead_tuples is disabled"));
3458 }
3459}
3460
3461/*
3462 * Return true iff 'rv' is a member of the list.
3463 */
3464static bool
3466{
3468 {
3469 if (equal(relinfo->rv, rv))
3470 return true;
3471 }
3472
3473 return false;
3474}
3475
3476/*
3477 * Get the list of tables and sequences which belong to specified publications
3478 * on the publisher connection.
3479 *
3480 * Note that we don't support the case where the column list is different for
3481 * the same table in different publications to avoid sending unwanted column
3482 * information for some of the rows. This can happen when both the column
3483 * list and row filter are specified for different publications.
3484 */
3485static List *
3487{
3488 WalRcvExecResult *res;
3489 StringInfoData cmd;
3490 TupleTableSlot *slot;
3494 bool check_columnlist = (server_version >= 150000);
3495 int column_count = check_columnlist ? 4 : 3;
3496 StringInfoData pub_names;
3497
3498 initStringInfo(&cmd);
3499 initStringInfo(&pub_names);
3500
3501 /* Build the pub_names comma-separated string. */
3502 GetPublicationsStr(publications, &pub_names, true);
3503
3504 /* Get the list of relations from the publisher */
3505 if (server_version >= 160000)
3506 {
3508
3509 /*
3510 * From version 16, we allowed passing multiple publications to the
3511 * function pg_get_publication_tables. This helped to filter out the
3512 * partition table whose ancestor is also published in this
3513 * publication array.
3514 *
3515 * Join pg_get_publication_tables with pg_publication to exclude
3516 * non-existing publications.
3517 *
3518 * Note that attrs are always stored in sorted order so we don't need
3519 * to worry if different publications have specified them in a
3520 * different order. See pub_collist_validate.
3521 */
3522 appendStringInfo(&cmd, "SELECT DISTINCT n.nspname, c.relname, c.relkind, gpt.attrs\n"
3523 " FROM pg_class c\n"
3524 " JOIN pg_namespace n ON n.oid = c.relnamespace\n"
3525 " JOIN ( SELECT (pg_get_publication_tables(VARIADIC array_agg(pubname::text))).*\n"
3526 " FROM pg_publication\n"
3527 " WHERE pubname IN ( %s )) AS gpt\n"
3528 " ON gpt.relid = c.oid\n",
3529 pub_names.data);
3530
3531 /* From version 19, inclusion of sequences in the target is supported */
3532 if (server_version >= 190000)
3533 appendStringInfo(&cmd,
3534 "UNION ALL\n"
3535 " SELECT DISTINCT s.schemaname, s.sequencename, " CppAsString2(RELKIND_SEQUENCE) "::\"char\" AS relkind, NULL::int2vector AS attrs\n"
3536 " FROM pg_catalog.pg_publication_sequences s\n"
3537 " WHERE s.pubname IN ( %s )",
3538 pub_names.data);
3539 }
3540 else
3541 {
3543 appendStringInfoString(&cmd, "SELECT DISTINCT t.schemaname, t.tablename, " CppAsString2(RELKIND_RELATION) "::\"char\" AS relkind \n");
3544
3545 /* Get column lists for each relation if the publisher supports it */
3546 if (check_columnlist)
3547 appendStringInfoString(&cmd, ", t.attnames\n");
3548
3549 appendStringInfo(&cmd, "FROM pg_catalog.pg_publication_tables t\n"
3550 " WHERE t.pubname IN ( %s )",
3551 pub_names.data);
3552 }
3553
3554 pfree(pub_names.data);
3555
3557 pfree(cmd.data);
3558
3559 if (res->status != WALRCV_OK_TUPLES)
3560 ereport(ERROR,
3562 errmsg("could not receive list of replicated tables from the publisher: %s",
3563 res->err)));
3564
3565 /* Process tables. */
3567 while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
3568 {
3569 char *nspname;
3570 char *relname;
3571 bool isnull;
3572 char relkind;
3574
3575 nspname = TextDatumGetCString(slot_getattr(slot, 1, &isnull));
3576 Assert(!isnull);
3577 relname = TextDatumGetCString(slot_getattr(slot, 2, &isnull));
3578 Assert(!isnull);
3579 relkind = DatumGetChar(slot_getattr(slot, 3, &isnull));
3580 Assert(!isnull);
3581
3582 relinfo->rv = makeRangeVar(nspname, relname, -1);
3583 relinfo->relkind = relkind;
3584
3585 if (relkind != RELKIND_SEQUENCE &&
3588 ereport(ERROR,
3590 errmsg("cannot use different column lists for table \"%s.%s\" in different publications",
3591 nspname, relname));
3592 else
3594
3595 ExecClearTuple(slot);
3596 }
3598
3600
3601 return relationlist;
3602}
3603
3604/*
3605 * This is to report the connection failure while dropping replication slots.
3606 * Here, we report the WARNING for all tablesync slots so that user can drop
3607 * them manually, if required.
3608 */
3609static void
3610ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err)
3611{
3612 ListCell *lc;
3613
3614 foreach(lc, rstates)
3615 {
3617 Oid relid = rstate->relid;
3618
3619 /* Only cleanup resources of tablesync workers */
3620 if (!OidIsValid(relid))
3621 continue;
3622
3623 /*
3624 * Caller needs to ensure that relstate doesn't change underneath us.
3625 * See DropSubscription where we get the relstates.
3626 */
3627 if (rstate->state != SUBREL_STATE_SYNCDONE)
3628 {
3629 char syncslotname[NAMEDATALEN] = {0};
3630
3632 sizeof(syncslotname));
3633 elog(WARNING, "could not drop tablesync replication slot \"%s\"",
3634 syncslotname);
3635 }
3636 }
3637
3638 ereport(ERROR,
3640 errmsg("could not connect to publisher when attempting to drop replication slot \"%s\": %s",
3641 slotname, err),
3642 /* translator: %s is an SQL ALTER command */
3643 errhint("Use %s to disable the subscription, and then use %s to disassociate it from the slot.",
3644 "ALTER SUBSCRIPTION ... DISABLE",
3645 "ALTER SUBSCRIPTION ... SET (slot_name = NONE)")));
3646}
3647
3648/*
3649 * Check for duplicates in the given list of publications and error out if
3650 * found one. Add publications to datums as text datums, if datums is not
3651 * NULL.
3652 */
3653static void
3655{
3656 ListCell *cell;
3657 int j = 0;
3658
3659 foreach(cell, publist)
3660 {
3661 char *name = strVal(lfirst(cell));
3662 ListCell *pcell;
3663
3664 foreach(pcell, publist)
3665 {
3666 char *pname = strVal(lfirst(pcell));
3667
3668 if (pcell == cell)
3669 break;
3670
3671 if (strcmp(name, pname) == 0)
3672 ereport(ERROR,
3674 errmsg("publication name \"%s\" used more than once",
3675 pname)));
3676 }
3677
3678 if (datums)
3679 datums[j++] = CStringGetTextDatum(name);
3680 }
3681}
3682
3683/*
3684 * Merge current subscription's publications and user-specified publications
3685 * from ADD/DROP PUBLICATIONS.
3686 *
3687 * If addpub is true, we will add the list of publications into oldpublist.
3688 * Otherwise, we will delete the list of publications from oldpublist. The
3689 * returned list is a copy, oldpublist itself is not changed.
3690 *
3691 * subname is the subscription name, for error messages.
3692 */
3693static List *
3695{
3696 ListCell *lc;
3697
3699
3701
3702 foreach(lc, newpublist)
3703 {
3704 char *name = strVal(lfirst(lc));
3705 ListCell *lc2;
3706 bool found = false;
3707
3708 foreach(lc2, oldpublist)
3709 {
3710 char *pubname = strVal(lfirst(lc2));
3711
3712 if (strcmp(name, pubname) == 0)
3713 {
3714 found = true;
3715 if (addpub)
3716 ereport(ERROR,
3718 errmsg("publication \"%s\" is already in subscription \"%s\"",
3719 name, subname)));
3720 else
3722
3723 break;
3724 }
3725 }
3726
3727 if (addpub && !found)
3729 else if (!addpub && !found)
3730 ereport(ERROR,
3732 errmsg("publication \"%s\" is not in subscription \"%s\"",
3733 name, subname)));
3734 }
3735
3736 /*
3737 * XXX Probably no strong reason for this, but for now it's to make ALTER
3738 * SUBSCRIPTION ... DROP PUBLICATION consistent with SET PUBLICATION.
3739 */
3740 if (!oldpublist)
3741 ereport(ERROR,
3743 errmsg("cannot drop all the publications from a subscription")));
3744
3745 return oldpublist;
3746}
3747
3748/*
3749 * Extract the streaming mode value from a DefElem. This is like
3750 * defGetBoolean() but also accepts the special value of "parallel".
3751 */
3752char
3754{
3755 /*
3756 * If no parameter value given, assume "true" is meant.
3757 */
3758 if (!def->arg)
3759 return LOGICALREP_STREAM_ON;
3760
3761 /*
3762 * Allow 0, 1, "false", "true", "off", "on" or "parallel".
3763 */
3764 switch (nodeTag(def->arg))
3765 {
3766 case T_Integer:
3767 switch (intVal(def->arg))
3768 {
3769 case 0:
3770 return LOGICALREP_STREAM_OFF;
3771 case 1:
3772 return LOGICALREP_STREAM_ON;
3773 default:
3774 /* otherwise, error out below */
3775 break;
3776 }
3777 break;
3778 default:
3779 {
3780 char *sval = defGetString(def);
3781
3782 /*
3783 * The set of strings accepted here should match up with the
3784 * grammar's opt_boolean_or_string production.
3785 */
3786 if (pg_strcasecmp(sval, "false") == 0 ||
3787 pg_strcasecmp(sval, "off") == 0)
3788 return LOGICALREP_STREAM_OFF;
3789 if (pg_strcasecmp(sval, "true") == 0 ||
3790 pg_strcasecmp(sval, "on") == 0)
3791 return LOGICALREP_STREAM_ON;
3792 if (pg_strcasecmp(sval, "parallel") == 0)
3794 }
3795 break;
3796 }
3797
3798 ereport(ERROR,
3800 errmsg("%s requires a Boolean value or \"parallel\"",
3801 def->defname)));
3802 return LOGICALREP_STREAM_OFF; /* keep compiler quiet */
3803}
void check_can_set_role(Oid member, Oid role)
Definition acl.c:5374
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 TextDatumGetCString(d)
Definition builtins.h:99
#define NameStr(name)
Definition c.h:894
#define Assert(condition)
Definition c.h:1002
#define CppAsString2(x)
Definition c.h:565
int32_t int32
Definition c.h:679
uint32_t uint32
Definition c.h:683
#define OidIsValid(objectId)
Definition c.h:917
Oid GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
Definition catalog.c:475
bool track_commit_timestamp
Definition commit_ts.c:121
const char *const ConflictLogDestNames[]
Definition conflict.c:34
Oid create_conflict_log_table(Oid subid, char *subname, Oid subowner)
Definition conflict.c:147
ConflictLogDest GetConflictLogDest(const char *dest)
Definition conflict.c:210
ConflictLogDest
Definition conflict.h:90
@ CONFLICT_LOG_DEST_LOG
Definition conflict.h:91
#define CONFLICTS_LOGGED_TO_TABLE(dest)
Definition conflict.h:96
int32 defGetInt32(DefElem *def)
Definition define.c:148
char * defGetString(DefElem *def)
Definition define.c:34
bool defGetBoolean(DefElem *def)
Definition define.c:93
void errorConflictingDefElem(DefElem *defel, ParseState *pstate)
Definition define.c:370
void performDeletion(const ObjectAddress *object, DropBehavior behavior, int flags)
Definition dependency.c:279
@ DEPENDENCY_INTERNAL
Definition dependency.h:35
@ DEPENDENCY_NORMAL
Definition dependency.h:33
#define PERFORM_DELETION_SKIP_ORIGINAL
Definition dependency.h:95
#define PERFORM_DELETION_INTERNAL
Definition dependency.h:92
void load_file(const char *filename, bool restricted)
Definition dfmgr.c:149
int errcode(int sqlerrcode)
Definition elog.c:875
#define _(x)
Definition elog.c:96
#define LOG
Definition elog.h:32
int errhint(const char *fmt,...) pg_attribute_printf(1
int int errmsg_internal(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 DEBUG1
Definition elog.h:31
#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
int errdetail_plural(const char *fmt_singular, const char *fmt_plural, unsigned long n,...) pg_attribute_printf(1
bool equal(const void *a, const void *b)
Definition equalfuncs.c:223
void err(int eval, const char *fmt,...)
Definition err.c:43
void EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal)
void CheckSubscriptionRelkind(char localrelkind, char remoterelkind, const char *nspname, const char *relname)
TupleTableSlot * MakeSingleTupleTableSlot(TupleDesc tupdesc, const TupleTableSlotOps *tts_ops)
void ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
const TupleTableSlotOps TTSOpsMinimalTuple
Definition execTuples.c:86
#define palloc_object(type)
Definition fe_memutils.h:89
#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
ForeignServer * GetForeignServer(Oid serverid)
Definition foreign.c:114
Oid MyDatabaseId
Definition globals.c:96
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
const char * str
HeapTuple heap_modify_tuple(HeapTuple tuple, TupleDesc tupleDesc, const Datum *replValues, const bool *replIsnull, const bool *doReplace)
Definition heaptuple.c:1118
HeapTuple heap_form_tuple(TupleDesc tupleDescriptor, const Datum *values, const bool *isnull)
Definition heaptuple.c:1025
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
void CatalogTupleInsert(Relation heapRel, HeapTuple tup)
Definition indexing.c:233
void CatalogTupleDelete(Relation heapRel, const ItemPointerData *tid)
Definition indexing.c:365
long val
Definition informix.c:689
int j
Definition isn.c:78
int i
Definition isn.c:77
List * logicalrep_workers_find(Oid subid, bool only_running, bool acquire_lock)
Definition launcher.c:303
void ApplyLauncherWakeupAtCommit(void)
Definition launcher.c:1185
void logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
Definition launcher.c:662
void ApplyLauncherForgetWorkerStartTime(Oid subid)
Definition launcher.c:1155
List * lappend(List *list, void *datum)
Definition list.c:339
List * list_append_unique(List *list, void *datum)
Definition list.c:1343
List * list_copy(const List *oldlist)
Definition list.c:1573
void list_free(List *list)
Definition list.c:1546
void LockSharedObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode)
Definition lmgr.c:1088
bool LockHeldByMe(const LOCKTAG *locktag, LOCKMODE lockmode, bool orstronger)
Definition lock.c:640
#define NoLock
Definition lockdefs.h:34
#define AccessExclusiveLock
Definition lockdefs.h:43
#define AccessShareLock
Definition lockdefs.h:36
#define RowExclusiveLock
Definition lockdefs.h:38
#define SET_LOCKTAG_OBJECT(locktag, dboid, classoid, objoid, objsubid)
Definition locktag.h:162
char * get_rel_name(Oid relid)
Definition lsyscache.c:2242
char * get_database_name(Oid dbid)
Definition lsyscache.c:1392
char get_rel_relkind(Oid relid)
Definition lsyscache.c:2317
Oid get_rel_namespace(Oid relid)
Definition lsyscache.c:2266
char * get_qualified_objname(Oid nspid, char *objname)
Definition lsyscache.c:3720
char * get_namespace_name(Oid nspid)
Definition lsyscache.c:3682
RangeVar * makeRangeVar(char *schemaname, char *relname, int location)
Definition makefuncs.c:473
char * pstrdup(const char *in)
Definition mcxt.c:1910
void pfree(void *pointer)
Definition mcxt.c:1619
void * palloc(Size size)
Definition mcxt.c:1390
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
#define RangeVarGetRelid(relation, lockmode, missing_ok)
Definition namespace.h:98
#define nodeTag(nodeptr)
Definition nodes.h:137
static char * errmsg
#define InvokeObjectPostCreateHook(classId, objectId, subId)
#define InvokeObjectPostAlterHook(classId, objectId, subId)
#define InvokeObjectDropHook(classId, objectId, subId)
#define ObjectAddressSet(addr, class_id, object_id)
int oid_cmp(const void *p1, const void *p2)
Definition oid.c:287
ReplOriginId replorigin_create(const char *roname)
Definition origin.c:274
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
void replorigin_drop_by_name(const char *name, bool missing_ok, bool nowait)
Definition origin.c:459
@ 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
@ DROP_CASCADE
@ OBJECT_DATABASE
@ OBJECT_FOREIGN_SERVER
@ OBJECT_SUBSCRIPTION
#define ACL_CREATE
Definition parsenodes.h:85
static AmcheckOptions opts
Definition pg_amcheck.c:112
NameData relname
Definition pg_class.h:40
#define NAMEDATALEN
void recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior)
Definition pg_depend.c:51
long deleteDependencyRecordsFor(Oid classId, Oid objectId, bool skipExtensionDeps)
Definition pg_depend.c:314
long deleteDependencyRecordsForSpecific(Oid classId, Oid objectId, char deptype, Oid refclassId, Oid refobjectId)
Definition pg_depend.c:411
static int server_version
Definition pg_dumpall.c:109
#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_delete_current(lst, var_or_cell)
Definition pg_list.h:423
#define foreach_ptr(type, var, lst)
Definition pg_list.h:501
Datum pg_lsn_in(PG_FUNCTION_ARGS)
Definition pg_lsn.c:64
static Datum LSNGetDatum(XLogRecPtr X)
Definition pg_lsn.h:31
static XLogRecPtr DatumGetLSN(Datum X)
Definition pg_lsn.h:25
void changeDependencyOnOwner(Oid classId, Oid objectId, Oid newOwnerId)
void deleteSharedDependencyRecordsFor(Oid classId, Oid objectId, int32 objectSubId)
void recordDependencyOnOwner(Oid classId, Oid objectId, Oid owner)
void UpdateSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn, bool already_locked)
Subscription * GetSubscription(Oid subid, bool missing_ok, bool conninfo_needed, bool conninfo_aclcheck)
void RemoveSubscriptionRel(Oid subid, Oid relid)
char GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn)
void GetPublicationsStr(List *publications, StringInfo dest, bool quote_literal)
void AddSubscriptionRelState(Oid subid, Oid relid, char state, XLogRecPtr sublsn, bool retain_lock)
List * GetSubscriptionRelations(Oid subid, bool tables, bool sequences, bool not_ready)
NameData subname
END_CATALOG_STRUCT typedef FormData_pg_subscription * Form_pg_subscription
Oid subconflictlogrelid
static char buf[DEFAULT_XLOG_SEG_SIZE]
void pgstat_drop_subscription(Oid subid)
void pgstat_create_subscription(Oid subid)
int pg_strcasecmp(const char *s1, const char *s2)
#define qsort(a, b, c, d)
Definition port.h:496
static bool DatumGetBool(Datum X)
Definition postgres.h:100
static Name DatumGetName(Datum X)
Definition postgres.h:393
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 char DatumGetChar(Datum X)
Definition postgres.h:122
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 InvalidOid
unsigned int Oid
char * c
static int fb(int x)
char * psprintf(const char *fmt,...)
Definition psprintf.c:43
char * quote_literal_cstr(const char *rawstr)
Definition quote.c:101
#define RelationGetDescr(relation)
Definition rel.h:542
bool ReplicationSlotValidateName(const char *name, bool allow_reserved_name, int elevel)
Definition slot.c:265
#define ERRCODE_DUPLICATE_OBJECT
Definition streamutil.c:30
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition stringinfo.c:242
void initStringInfo(StringInfo str)
Definition stringinfo.c:97
char * defname
Definition parsenodes.h:862
Node * arg
Definition parsenodes.h:863
char * servername
Definition foreign.h:40
Definition pg_list.h:54
LogicalRepWorkerType type
char * relname
Definition primnodes.h:84
char * schemaname
Definition primnodes.h:81
uint32 specified_opts
ConflictLogDest conflictlogdest
bool retaindeadtuples
char * wal_receiver_timeout
char * synchronous_commit
int32 maxretention
char * slot_name
XLogRecPtr lsn
bool passwordrequired
Tuplestorestate * tuplestore
TupleDesc tupledesc
WalRcvExecStatus status
void DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
#define SUBOPT_STREAMING
char defGetStreamingMode(DefElem *def)
#define SUBOPT_CREATE_SLOT
#define SUBOPT_PASSWORD_REQUIRED
#define SUBOPT_SYNCHRONOUS_COMMIT
#define SUBOPT_ENABLED
static void check_duplicates_in_publist(List *publist, Datum *datums)
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)
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)
#define SUBOPT_RUN_AS_OWNER
static void drop_sub_conflict_log_table(Oid subid, char *subname, Oid subconflictlogrelid)
#define SUBOPT_SLOT_NAME
static char * construct_subserver_conninfo(Oid subserver, Oid subowner, char **err)
static List * fetch_relation_list(WalReceiverConn *wrconn, List *publications)
#define SUBOPT_COPY_DATA
#define SUBOPT_TWOPHASE_COMMIT
static void AlterSubscription_refresh(Subscription *sub, bool copy_data, List *validate_publications)
static void ReportSlotConnectionError(List *rstates, Oid subid, char *slotname, char *err)
#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)
static void AlterSubscriptionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
ObjectAddress AlterSubscriptionOwner(const char *name, Oid newOwnerId)
void ReplicationSlotDropAtPubNode(WalReceiverConn *wrconn, char *slotname, bool missing_ok)
#define appendQuotedIdentifier(b, s)
void AlterSubscriptionOwner_oid(Oid subid, Oid newOwnerId)
#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)
static bool list_member_rangevar(const List *list, RangeVar *rv)
#define SUBOPT_BINARY
#define IsSet(val, bits)
#define SUBOPT_REFRESH
static void appendQuotedString(StringInfo buf, const char *str, char quote)
#define SUBOPT_CONNECT
static void check_pub_dead_tuple_retention(WalReceiverConn *wrconn)
ObjectAddress AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt, bool isTopLevel)
bool superuser_arg(Oid roleid)
Definition superuser.c:57
bool superuser(void)
Definition superuser.c:47
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
#define SearchSysCacheCopy1(cacheId, key1)
Definition syscache.h:91
#define SearchSysCacheCopy2(cacheId, key1, key2)
Definition syscache.h:93
#define GetSysCacheOid2(cacheId, oidcol, key1, key2)
Definition syscache.h:111
void table_close(Relation relation, LOCKMODE lockmode)
Definition table.c:126
Relation table_open(Oid relationId, LOCKMODE lockmode)
Definition table.c:40
void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode)
void ReplicationSlotNameForTablesync(Oid suboid, Oid relid, char *syncslotname, Size szslot)
Definition tablesync.c:1236
void UpdateTwoPhaseState(Oid suboid, char new_state)
Definition tablesync.c:1681
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
static TupleTableSlot * ExecClearTuple(TupleTableSlot *slot)
Definition tuptable.h:476
bool LookupGXactBySubid(Oid subid)
Definition twophase.c:2805
String * makeString(char *str)
Definition value.c:63
#define intVal(v)
Definition value.h:79
#define strVal(v)
Definition value.h:82
const char * name
static WalReceiverConn * wrconn
Definition walreceiver.c:95
#define walrcv_connect(conninfo, replication, logical, must_use_password, appname, err)
@ WALRCV_OK_COMMAND
@ WALRCV_ERROR
@ WALRCV_OK_TUPLES
#define walrcv_create_slot(conn, slotname, temporary, two_phase, failover, snapshot_action, lsn)
static void walrcv_clear_result(WalRcvExecResult *walres)
#define walrcv_server_version(conn)
#define walrcv_check_conninfo(conninfo, must_use_password)
#define walrcv_alter_slot(conn, slotname, failover, two_phase)
#define walrcv_exec(conn, exec, nRetTypes, retTypes)
#define walrcv_disconnect(conn)
@ CRS_NOEXPORT_SNAPSHOT
Definition walsender.h:23
@ WORKERTYPE_TABLESYNC
@ WORKERTYPE_SEQUENCESYNC
void PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
Definition xact.c:3701
int wal_level
Definition xlog.c:138
@ WAL_LEVEL_REPLICA
Definition xlog.h:77
#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
#define InvalidXLogRecPtr
Definition xlogdefs.h:28