Skip to content

Commit 85ddcc2

Browse files
author
Amit Kapila
committed
Support existing publications in pg_createsubscriber.
Allow pg_createsubscriber to reuse existing publications instead of failing when they already exist on the publisher. Previously, pg_createsubscriber would fail if any specified publication already existed. Now, existing publications are reused as-is with their current configuration, and non-existing publications are created automatically with FOR ALL TABLES. This change provides flexibility when working with mixed scenarios of existing and new publications. Users should verify that existing publications have the desired configuration before reusing them, and can use --dry-run with verbose mode to see which publications will be reused and which will be created. Only publications created by pg_createsubscriber are cleaned up during error cleanup operations. Pre-existing publications are preserved unless '--clean=publications' is explicitly specified, which drops all publications. This feature would be helpful for pub-sub configurations where users want to subscribe to a subset of tables from the publisher. Author: Shubham Khanna <khannashubham1197@gmail.com> Reviewed-by: Euler Taveira <euler@eulerto.com> Reviewed-by: Peter Smith <smithpb2250@gmail.com> Reviewed-by: Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com Reviewed-by: Chao Li <li.evan.chao@gmail.com> Reviewed-by: vignesh C <vignesh21@gmail.com> Reviewed-by: tianbing <tian_bing_0531@163.com> Discussion: https://postgr.es/m/CAHv8Rj%2BsxWutv10WiDEAPZnygaCbuY2RqiLMj2aRMH-H3iZwyA%40mail.gmail.com
1 parent f4e7971 commit 85ddcc2

File tree

3 files changed

+132
-25
lines changed

3 files changed

+132
-25
lines changed

doc/src/sgml/ref/pg_createsubscriber.sgml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,14 @@ PostgreSQL documentation
285285
a generated name is assigned to the publication name. This option cannot
286286
be used together with <option>--all</option>.
287287
</para>
288+
<para>
289+
If a specified publication already exists on the publisher, it is reused.
290+
It is useful to partially replicate the database if the specified
291+
publication includes a list of tables. If the publication does not exist,
292+
it is automatically created with <literal>FOR ALL TABLES</literal>. Use
293+
<option>--dry-run</option> option to preview which publications will be
294+
reused and which will be created.
295+
</para>
288296
</listitem>
289297
</varlistentry>
290298

src/bin/pg_basebackup/pg_createsubscriber.c

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ static void stop_standby_server(const char *datadir);
116116
static void wait_for_end_recovery(const char *conninfo,
117117
const struct CreateSubscriberOptions *opt);
118118
static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
119+
static bool find_publication(PGconn *conn, const char *pubname, const char *dbname);
119120
static void drop_publication(PGconn *conn, const char *pubname,
120121
const char *dbname, bool *made_publication);
121122
static void check_and_drop_publications(PGconn *conn, struct LogicalRepInfo *dbinfo);
@@ -763,6 +764,39 @@ generate_object_name(PGconn *conn)
763764
return objname;
764765
}
765766

767+
/*
768+
* Does the publication exist in the specified database?
769+
*/
770+
static bool
771+
find_publication(PGconn *conn, const char *pubname, const char *dbname)
772+
{
773+
PQExpBuffer str = createPQExpBuffer();
774+
PGresult *res;
775+
boolfound = false;
776+
char *pubname_esc = PQescapeLiteral(conn, pubname, strlen(pubname));
777+
778+
appendPQExpBuffer(str,
779+
"SELECT 1 FROM pg_catalog.pg_publication "
780+
"WHERE pubname = %s",
781+
pubname_esc);
782+
res = PQexec(conn, str->data);
783+
if (PQresultStatus(res) != PGRES_TUPLES_OK)
784+
{
785+
pg_log_error("could not find publication \"%s\" in database \"%s\": %s",
786+
pubname, dbname, PQerrorMessage(conn));
787+
disconnect_database(conn, true);
788+
}
789+
790+
if (PQntuples(res) == 1)
791+
found = true;
792+
793+
PQclear(res);
794+
PQfreemem(pubname_esc);
795+
destroyPQExpBuffer(str);
796+
797+
return found;
798+
}
799+
766800
/*
767801
* Create the publications and replication slots in preparation for logical
768802
* replication. Returns the LSN from latest replication slot. It will be the
@@ -799,13 +833,25 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
799833
if (num_replslots == 0)
800834
dbinfo[i].replslotname = pg_strdup(dbinfo[i].subname);
801835

802-
/*
803-
* Create publication on publisher. This step should be executed
804-
* *before* promoting the subscriber to avoid any transactions between
805-
* consistent LSN and the new publication rows (such transactions
806-
* wouldn't see the new publication rows resulting in an error).
807-
*/
808-
create_publication(conn, &dbinfo[i]);
836+
if (find_publication(conn, dbinfo[i].pubname, dbinfo[i].dbname))
837+
{
838+
/* Reuse existing publication on publisher. */
839+
pg_log_info("use existing publication \"%s\" in database \"%s\"",
840+
dbinfo[i].pubname, dbinfo[i].dbname);
841+
/* Don't remove pre-existing publication if an error occurs. */
842+
dbinfo[i].made_publication = false;
843+
}
844+
else
845+
{
846+
/*
847+
* Create publication on publisher. This step should be executed
848+
* *before* promoting the subscriber to avoid any transactions
849+
* between consistent LSN and the new publication rows (such
850+
* transactions wouldn't see the new publication rows resulting in
851+
* an error).
852+
*/
853+
create_publication(conn, &dbinfo[i]);
854+
}
809855

810856
/* Create replication slot on publisher */
811857
if (lsn)
@@ -1749,11 +1795,10 @@ drop_publication(PGconn *conn, const char *pubname, const char *dbname,
17491795
/*
17501796
* Retrieve and drop the publications.
17511797
*
1752-
* Since the publications were created before the consistent LSN, they
1753-
* remain on the subscriber even after the physical replica is
1754-
* promoted. Remove these publications from the subscriber because
1755-
* they have no use. Additionally, if requested, drop all pre-existing
1756-
* publications.
1798+
* Publications copied during physical replication remain on the subscriber
1799+
* after promotion. If --clean=publications is specified, drop all existing
1800+
* publications in the subscriber database. Otherwise, only drop publications
1801+
* that were created by pg_createsubscriber during this operation.
17571802
*/
17581803
static void
17591804
check_and_drop_publications(PGconn *conn, struct LogicalRepInfo *dbinfo)
@@ -1785,14 +1830,24 @@ check_and_drop_publications(PGconn *conn, struct LogicalRepInfo *dbinfo)
17851830

17861831
PQclear(res);
17871832
}
1788-
1789-
/*
1790-
* In dry-run mode, we don't create publications, but we still try to drop
1791-
* those to provide necessary information to the user.
1792-
*/
1793-
if (!drop_all_pubs || dry_run)
1794-
drop_publication(conn, dbinfo->pubname, dbinfo->dbname,
1795-
&dbinfo->made_publication);
1833+
else
1834+
{
1835+
/* Drop publication only if it was created by this tool */
1836+
if (dbinfo->made_publication)
1837+
{
1838+
drop_publication(conn, dbinfo->pubname, dbinfo->dbname,
1839+
&dbinfo->made_publication);
1840+
}
1841+
else
1842+
{
1843+
if (dry_run)
1844+
pg_log_info("dry-run: would preserve existing publication \"%s\" in database \"%s\"",
1845+
dbinfo->pubname, dbinfo->dbname);
1846+
else
1847+
pg_log_info("preserve existing publication \"%s\" in database \"%s\"",
1848+
dbinfo->pubname, dbinfo->dbname);
1849+
}
1850+
}
17961851
}
17971852

17981853
/*

src/bin/pg_basebackup/t/040_pg_createsubscriber.pl

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,10 +443,17 @@ sub generate_db
443443
is(scalar(() = $stderr =~ /would create subscription/g),
444444
3, "verify subscriptions are created for all databases");
445445

446+
# Create a user-defined publication, and a table that is not a member of that
447+
# publication.
448+
$node_p->safe_psql($db1, qq(
449+
CREATE PUBLICATION test_pub3 FOR TABLE tbl1;
450+
CREATE TABLE not_replicated (a int);
451+
));
452+
446453
# Run pg_createsubscriber on node S. --verbose is used twice
447454
# to show more information.
448-
# In passing, also test the --enable-two-phase option and
449-
# --clean option
455+
#
456+
# Test two phase and clean options. Use pre-existing publication.
450457
command_ok(
451458
[
452459
'pg_createsubscriber',
@@ -456,7 +463,7 @@ sub generate_db
456463
'--publisher-server' => $node_p->connstr($db1),
457464
'--socketdir' => $node_s->host,
458465
'--subscriber-port' => $node_s->port,
459-
'--publication' => 'pub1',
466+
'--publication' => 'test_pub3',
460467
'--publication' => 'pub2',
461468
'--replication-slot' => 'replslot1',
462469
'--replication-slot' => 'replslot2',
@@ -478,13 +485,16 @@ sub generate_db
478485
# Insert rows on P
479486
$node_p->safe_psql($db1, "INSERT INTO tbl1 VALUES('third row')");
480487
$node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
488+
$node_p->safe_psql($db1, "INSERT INTO not_replicated VALUES(0)");
481489

482490
# Start subscriber
483491
$node_s->start;
484492

485493
# Confirm publications are removed from the subscriber node
486-
is($node_s->safe_psql($db1, "SELECT COUNT(*) FROM pg_publication;"),
487-
'0', 'all publications on subscriber have been removed');
494+
is($node_s->safe_psql($db1, 'SELECT COUNT(*) FROM pg_publication'),
495+
'0', 'all publications were removed from db1');
496+
is($node_s->safe_psql($db2, 'SELECT COUNT(*) FROM pg_publication'),
497+
'0', 'all publications were removed from db2');
488498

489499
# Verify that all subtwophase states are pending or enabled,
490500
# e.g. there are no subscriptions where subtwophase is disabled ('d')
@@ -525,6 +535,9 @@ sub generate_db
525535
second row
526536
third row),
527537
"logical replication works in database $db1");
538+
$result = $node_s->safe_psql($db1, 'SELECT * FROM not_replicated');
539+
is($result, qq(),
540+
"table is not replicated in database $db1");
528541

529542
# Check result in database $db2
530543
$result = $node_s->safe_psql($db2, 'SELECT * FROM tbl2');
@@ -537,6 +550,37 @@ sub generate_db
537550
'SELECT system_identifier FROM pg_control_system()');
538551
isnt($sysid_p, $sysid_s, 'system identifier was changed');
539552

553+
# Verify that pub2 was created in $db2
554+
is($node_p->safe_psql($db2, "SELECT COUNT(*) FROM pg_publication WHERE pubname = 'pub2'"),
555+
'1', "publication pub2 was created in $db2");
556+
557+
# Get subscription and publication names
558+
$result = $node_s->safe_psql(
559+
'postgres', qq(
560+
SELECT subname, subpublications FROM pg_subscription WHERE subname ~ '^pg_createsubscriber_'
561+
ORDER BY subpublications;
562+
));
563+
like(
564+
$result,
565+
qr/^pg_createsubscriber_\d+_[0-9a-f]+ \|\{pub2\}\n
566+
pg_createsubscriber_\d+_[0-9a-f]+ \|\{test_pub3\}$/x,
567+
'subscription and publication names are ok');
568+
569+
# Verify that the correct publications are being used
570+
$result = $node_s->safe_psql(
571+
'postgres', qq(
572+
SELECT d.datname, s.subpublications
573+
FROM pg_subscription s
574+
JOIN pg_database d ON d.oid = s.subdbid
575+
WHERE subname ~ '^pg_createsubscriber_'
576+
ORDER BY s.subdbid
577+
)
578+
);
579+
580+
is($result, qq($db1|{test_pub3}
581+
$db2|{pub2}),
582+
"subscriptions use the correct publications");
583+
540584
# clean up
541585
$node_p->teardown_node;
542586
$node_s->teardown_node;

0 commit comments

Comments
 (0)