From aa09f2e978ec5c713e3b4e1716618a728176ed7b Mon Sep 17 00:00:00 2001 From: Khanna Date: Thu, 20 Mar 2025 13:52:27 +0530 Subject: [PATCH v17] Enhance 'pg_createsubscriber' to fetch and append all databases This patch enhances the 'pg_createsubscriber' utility by adding the '--all' option. When '--all' is specified, the tool queries the source server (publisher) for all databases and creates subscriptions on the target server (subscriber) for databases with matching names. This simplifies the process of converting a physical standby to a logical subscriber, particularly during upgrades. The options '--database', '--publication', '--subscription', and '--replication-slot' cannot be used when '--all' is specified. --- doc/src/sgml/ref/pg_createsubscriber.sgml | 35 +++++-- src/bin/pg_basebackup/pg_createsubscriber.c | 92 ++++++++++++++++++- .../t/040_pg_createsubscriber.pl | 69 ++++++++++++++ 3 files changed, 185 insertions(+), 11 deletions(-) diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml index 380d0b1c35c..3220b0cfa7b 100644 --- a/doc/src/sgml/ref/pg_createsubscriber.sgml +++ b/doc/src/sgml/ref/pg_createsubscriber.sgml @@ -87,6 +87,22 @@ PostgreSQL documentation command-line arguments: + + + + + + For every non-template database on the source server, create one + subscription on the target server in the database with the same name. + Automatically generated names for subscriptions, publications, and + replication slots are used when this option is specified. This option + cannot be used along with , + , , or + . + + + + @@ -94,10 +110,12 @@ PostgreSQL documentation The name of the database in which to create a subscription. Multiple databases can be selected by writing multiple - switches. If option is not provided, the database - name will be obtained from option. If the database - name is not specified in either the option or - option, an error will be reported. + switches. This option cannot be used together with . + If option is not provided, the database name will be + obtained from option. If the database name is not + specified in either the option, or the + option, and + an error will be reported. @@ -253,7 +271,8 @@ PostgreSQL documentation names must match the number of specified databases, otherwise an error is reported. The order of the multiple publication name switches must match the order of database switches. If this option is not specified, - a generated name is assigned to the publication name. + a generated name is assigned to the publication name. This option cannot + be used together with . @@ -269,7 +288,8 @@ PostgreSQL documentation otherwise an error is reported. The order of the multiple replication slot name switches must match the order of database switches. If this option is not specified, the subscription name is assigned to the - replication slot name. + replication slot name. This option cannot be used together with + . @@ -284,7 +304,8 @@ PostgreSQL documentation names must match the number of specified databases, otherwise an error is reported. The order of the multiple subscription name switches must match the order of database switches. If this option is not specified, - a generated name is assigned to the subscription name. + a generated name is assigned to the subscription name. This option cannot + be used together with . diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c index d067eb44e6c..831408872b9 100644 --- a/src/bin/pg_basebackup/pg_createsubscriber.c +++ b/src/bin/pg_basebackup/pg_createsubscriber.c @@ -45,6 +45,7 @@ struct CreateSubscriberOptions SimpleStringList sub_names; /* list of subscription names */ SimpleStringList replslot_names; /* list of replication slot names */ int recovery_timeout; /* stop recovery after this time */ + bool all_dbs; /* --all option was specified */ SimpleStringList objecttypes_to_remove; /* list of object types to remove */ }; @@ -124,6 +125,7 @@ static void check_and_drop_existing_subscriptions(PGconn *conn, const struct LogicalRepInfo *dbinfo); static void drop_existing_subscriptions(PGconn *conn, const char *subname, const char *dbname); +static void fetch_source_databases(struct CreateSubscriberOptions *opt); #define USEC_PER_SEC 1000000 #define WAIT_INTERVAL 1 /* 1 second */ @@ -243,6 +245,7 @@ usage(void) printf(_("Usage:\n")); printf(_(" %s [OPTION]...\n"), progname); printf(_("\nOptions:\n")); + printf(_(" -a, --all create subscriptions for all non-template source databases\n")); printf(_(" -d, --database=DBNAME database in which to create a subscription\n")); printf(_(" -D, --pgdata=DATADIR location for the subscriber data directory\n")); printf(_(" -n, --dry-run dry run, just show what would be done\n")); @@ -1959,11 +1962,60 @@ enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo) destroyPQExpBuffer(str); } +/* + * If --all is specified, fetch a list of all user-created databases from the + * source server. Internally, this is treated as if the user specified multiple + * --database options, one for each source database. + */ +static void +fetch_source_databases(struct CreateSubscriberOptions *opt) +{ + PGconn *conn; + PGresult *res; + + /* Establish a connection to the source server */ + conn = connect_database(opt->pub_conninfo_str, true); + + res = PQexec(conn, "SELECT datname FROM pg_database WHERE datistemplate = false AND datallowconn = true"); + + /* Check for errors during query execution */ + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + pg_log_error("could not obtain a list of databases: %s", PQresultErrorMessage(res)); + PQclear(res); + disconnect_database(conn, true); + } + + /* Process the query result */ + for (int i = 0; i < PQntuples(res); i++) + { + const char *dbname = PQgetvalue(res, i, 0); + + simple_string_list_append(&opt->database_names, dbname); + + /* Increment num_dbs to reflect multiple --database options */ + num_dbs++; + } + + /* Error if no databases were found on the source server */ + if (num_dbs == 0) + { + pg_log_error("no suitable databases found on the source server"); + pg_log_error_hint("Ensure that there are non-template and connectable databases on the source server."); + PQclear(res); + disconnect_database(conn, true); + } + + PQclear(res); + disconnect_database(conn, false); +} + int main(int argc, char **argv) { static struct option long_options[] = { + {"all", no_argument, NULL, 'a'}, {"database", required_argument, NULL, 'd'}, {"pgdata", required_argument, NULL, 'D'}, {"dry-run", no_argument, NULL, 'n'}, @@ -2034,6 +2086,7 @@ main(int argc, char **argv) 0 }; opt.recovery_timeout = 0; + opt.all_dbs = false; /* * Don't allow it to be run as root. It uses pg_ctl which does not allow @@ -2051,11 +2104,14 @@ main(int argc, char **argv) get_restricted_token(); - while ((c = getopt_long(argc, argv, "d:D:np:P:R:s:t:TU:v", + while ((c = getopt_long(argc, argv, "ad:D:np:P:R:s:t:TU:v", long_options, &option_index)) != -1) { switch (c) { + case 'a': + opt.all_dbs = true; + break; case 'd': if (!simple_string_list_member(&opt.database_names, optarg)) { @@ -2149,6 +2205,28 @@ main(int argc, char **argv) } } + /* Validate that --all is not used with incompatible options */ + if (opt.all_dbs) + { + char *bad_switch = NULL; + + if (num_dbs > 0) + bad_switch = "--database"; + else if (num_pubs > 0) + bad_switch = "--publication"; + else if (num_replslots > 0) + bad_switch = "--replication-slot"; + else if (num_subs > 0) + bad_switch = "--subscription"; + + if (bad_switch) + { + pg_log_error("%s cannot be used with --all", bad_switch); + pg_log_error_hint("Try \"%s --help\" for more information.", progname); + exit(1); + } + } + /* Any non-option arguments? */ if (optind < argc) { @@ -2202,14 +2280,20 @@ main(int argc, char **argv) pg_log_info("validating subscriber connection string"); sub_base_conninfo = get_sub_conninfo(&opt); + /* + * Fetch all databases from the source (publisher) if --all is specified. + */ + if (opt.all_dbs) + fetch_source_databases(&opt); + if (opt.database_names.head == NULL) { pg_log_info("no database was specified"); /* - * If --database option is not provided, try to obtain the dbname from - * the publisher conninfo. If dbname parameter is not available, error - * out. + * If neither --database nor --all option is provided, try to obtain + * the dbname from the publisher conninfo. If dbname parameter is not + * available, error out. */ if (dbname_conninfo) { diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl index 2c9bd5bdb9e..f32d254b4e8 100644 --- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl +++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl @@ -386,6 +386,75 @@ command_ok( ], 'run pg_createsubscriber without --databases'); +# run pg_createsubscriber with '--database' and '--all' without '--dry-run' +# and verify the failure +command_fails_like( + [ + 'pg_createsubscriber', + '--verbose', + '--pgdata' => $node_s->data_dir, + '--publisher-server' => $node_p->connstr($db1), + '--socketdir' => $node_s->host, + '--subscriber-port' => $node_s->port, + '--database' => $db1, + '--all', + ], + qr/--database cannot be used with --all/, + 'fail if --database is used with --all'); + +# run pg_createsubscriber with '--publication' and '--all' and verify +# the failure +command_fails_like( + [ + 'pg_createsubscriber', + '--verbose', + '--dry-run', + '--pgdata' => $node_s->data_dir, + '--publisher-server' => $node_p->connstr($db1), + '--socketdir' => $node_s->host, + '--subscriber-port' => $node_s->port, + '--all', + '--publication' => 'pub1', + ], + qr/--publication cannot be used with --all/, + 'fail if --publication is used with --all'); + +# Create a new database on node_p +$node_p->safe_psql( + "postgres", qq( + CREATE DATABASE db1; +)); + +# run pg_createsubscriber with '--all' option +my ($stdout, $stderr) = run_command( + [ + 'pg_createsubscriber', + '--verbose', + '--dry-run', + '--recovery-timeout' => $PostgreSQL::Test::Utils::timeout_default, + '--pgdata' => $node_s->data_dir, + '--publisher-server' => $node_p->connstr($db1), + '--socketdir' => $node_s->host, + '--subscriber-port' => $node_s->port, + '--all', + ], + 'run pg_createsubscriber with --all'); + +like( + $stderr, + qr/.*pg_createsubscriber: creating publication .* in database \"postgres\"/, + "expanded commands for all databases"); +like( + $stderr, + qr/.*pg_createsubscriber: creating publication .* in database \"db1\"/, + "expanded commands for all databases"); + +# Drop the newly created database on node_p +$node_p->safe_psql( + "postgres", qq( + DROP DATABASE db1; +)); + # Run pg_createsubscriber on node S. --verbose is used twice # to show more information. # In passing, also test the --enable-two-phase option and -- 2.41.0.windows.3