LCOV - code coverage report
Current view: top level - src/bin/pg_upgrade - info.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 204 303 67.3 %
Date: 2024-04-20 01:11:48 Functions: 12 16 75.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  *  info.c
       3             :  *
       4             :  *  information support functions
       5             :  *
       6             :  *  Copyright (c) 2010-2024, PostgreSQL Global Development Group
       7             :  *  src/bin/pg_upgrade/info.c
       8             :  */
       9             : 
      10             : #include "postgres_fe.h"
      11             : 
      12             : #include "access/transam.h"
      13             : #include "catalog/pg_class_d.h"
      14             : #include "pg_upgrade.h"
      15             : 
      16             : static void create_rel_filename_map(const char *old_data, const char *new_data,
      17             :                                     const DbInfo *old_db, const DbInfo *new_db,
      18             :                                     const RelInfo *old_rel, const RelInfo *new_rel,
      19             :                                     FileNameMap *map);
      20             : static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
      21             :                                       bool is_new_db);
      22             : static void free_db_and_rel_infos(DbInfoArr *db_arr);
      23             : static void get_template0_info(ClusterInfo *cluster);
      24             : static void get_db_infos(ClusterInfo *cluster);
      25             : static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
      26             : static void free_rel_infos(RelInfoArr *rel_arr);
      27             : static void print_db_infos(DbInfoArr *db_arr);
      28             : static void print_rel_infos(RelInfoArr *rel_arr);
      29             : static void print_slot_infos(LogicalSlotInfoArr *slot_arr);
      30             : static void get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check);
      31             : static void get_db_subscription_count(DbInfo *dbinfo);
      32             : 
      33             : 
      34             : /*
      35             :  * gen_db_file_maps()
      36             :  *
      37             :  * generates a database mapping from "old_db" to "new_db".
      38             :  *
      39             :  * Returns a malloc'ed array of mappings.  The length of the array
      40             :  * is returned into *nmaps.
      41             :  */
      42             : FileNameMap *
      43          20 : gen_db_file_maps(DbInfo *old_db, DbInfo *new_db,
      44             :                  int *nmaps,
      45             :                  const char *old_pgdata, const char *new_pgdata)
      46             : {
      47             :     FileNameMap *maps;
      48             :     int         old_relnum,
      49             :                 new_relnum;
      50          20 :     int         num_maps = 0;
      51          20 :     bool        all_matched = true;
      52             : 
      53             :     /* There will certainly not be more mappings than there are old rels */
      54          20 :     maps = (FileNameMap *) pg_malloc(sizeof(FileNameMap) *
      55          20 :                                      old_db->rel_arr.nrels);
      56             : 
      57             :     /*
      58             :      * Each of the RelInfo arrays should be sorted by OID.  Scan through them
      59             :      * and match them up.  If we fail to match everything, we'll abort, but
      60             :      * first print as much info as we can about mismatches.
      61             :      */
      62          20 :     old_relnum = new_relnum = 0;
      63        2756 :     while (old_relnum < old_db->rel_arr.nrels ||
      64          20 :            new_relnum < new_db->rel_arr.nrels)
      65             :     {
      66        5472 :         RelInfo    *old_rel = (old_relnum < old_db->rel_arr.nrels) ?
      67        2736 :             &old_db->rel_arr.rels[old_relnum] : NULL;
      68        5472 :         RelInfo    *new_rel = (new_relnum < new_db->rel_arr.nrels) ?
      69        2736 :             &new_db->rel_arr.rels[new_relnum] : NULL;
      70             : 
      71             :         /* handle running off one array before the other */
      72        2736 :         if (!new_rel)
      73             :         {
      74             :             /*
      75             :              * old_rel is unmatched.  This should never happen, because we
      76             :              * force new rels to have TOAST tables if the old one did.
      77             :              */
      78           0 :             report_unmatched_relation(old_rel, old_db, false);
      79           0 :             all_matched = false;
      80           0 :             old_relnum++;
      81           0 :             continue;
      82             :         }
      83        2736 :         if (!old_rel)
      84             :         {
      85             :             /*
      86             :              * new_rel is unmatched.  This shouldn't really happen either, but
      87             :              * if it's a TOAST table, we can ignore it and continue
      88             :              * processing, assuming that the new server made a TOAST table
      89             :              * that wasn't needed.
      90             :              */
      91           0 :             if (strcmp(new_rel->nspname, "pg_toast") != 0)
      92             :             {
      93           0 :                 report_unmatched_relation(new_rel, new_db, true);
      94           0 :                 all_matched = false;
      95             :             }
      96           0 :             new_relnum++;
      97           0 :             continue;
      98             :         }
      99             : 
     100             :         /* check for mismatched OID */
     101        2736 :         if (old_rel->reloid < new_rel->reloid)
     102             :         {
     103             :             /* old_rel is unmatched, see comment above */
     104           0 :             report_unmatched_relation(old_rel, old_db, false);
     105           0 :             all_matched = false;
     106           0 :             old_relnum++;
     107           0 :             continue;
     108             :         }
     109        2736 :         else if (old_rel->reloid > new_rel->reloid)
     110             :         {
     111             :             /* new_rel is unmatched, see comment above */
     112           0 :             if (strcmp(new_rel->nspname, "pg_toast") != 0)
     113             :             {
     114           0 :                 report_unmatched_relation(new_rel, new_db, true);
     115           0 :                 all_matched = false;
     116             :             }
     117           0 :             new_relnum++;
     118           0 :             continue;
     119             :         }
     120             : 
     121             :         /*
     122             :          * Verify that rels of same OID have same name.  The namespace name
     123             :          * should always match, but the relname might not match for TOAST
     124             :          * tables (and, therefore, their indexes).
     125             :          */
     126        2736 :         if (strcmp(old_rel->nspname, new_rel->nspname) != 0 ||
     127        2736 :             strcmp(old_rel->relname, new_rel->relname) != 0)
     128             :         {
     129           0 :             pg_log(PG_WARNING, "Relation names for OID %u in database \"%s\" do not match: "
     130             :                    "old name \"%s.%s\", new name \"%s.%s\"",
     131             :                    old_rel->reloid, old_db->db_name,
     132             :                    old_rel->nspname, old_rel->relname,
     133             :                    new_rel->nspname, new_rel->relname);
     134           0 :             all_matched = false;
     135           0 :             old_relnum++;
     136           0 :             new_relnum++;
     137           0 :             continue;
     138             :         }
     139             : 
     140             :         /* OK, create a mapping entry */
     141        2736 :         create_rel_filename_map(old_pgdata, new_pgdata, old_db, new_db,
     142        2736 :                                 old_rel, new_rel, maps + num_maps);
     143        2736 :         num_maps++;
     144        2736 :         old_relnum++;
     145        2736 :         new_relnum++;
     146             :     }
     147             : 
     148          20 :     if (!all_matched)
     149           0 :         pg_fatal("Failed to match up old and new tables in database \"%s\"",
     150             :                  old_db->db_name);
     151             : 
     152          20 :     *nmaps = num_maps;
     153          20 :     return maps;
     154             : }
     155             : 
     156             : 
     157             : /*
     158             :  * create_rel_filename_map()
     159             :  *
     160             :  * fills a file node map structure and returns it in "map".
     161             :  */
     162             : static void
     163        2736 : create_rel_filename_map(const char *old_data, const char *new_data,
     164             :                         const DbInfo *old_db, const DbInfo *new_db,
     165             :                         const RelInfo *old_rel, const RelInfo *new_rel,
     166             :                         FileNameMap *map)
     167             : {
     168             :     /* In case old/new tablespaces don't match, do them separately. */
     169        2736 :     if (strlen(old_rel->tablespace) == 0)
     170             :     {
     171             :         /*
     172             :          * relation belongs to the default tablespace, hence relfiles should
     173             :          * exist in the data directories.
     174             :          */
     175        2736 :         map->old_tablespace = old_data;
     176        2736 :         map->old_tablespace_suffix = "/base";
     177             :     }
     178             :     else
     179             :     {
     180             :         /* relation belongs to a tablespace, so use the tablespace location */
     181           0 :         map->old_tablespace = old_rel->tablespace;
     182           0 :         map->old_tablespace_suffix = old_cluster.tablespace_suffix;
     183             :     }
     184             : 
     185             :     /* Do the same for new tablespaces */
     186        2736 :     if (strlen(new_rel->tablespace) == 0)
     187             :     {
     188        2736 :         map->new_tablespace = new_data;
     189        2736 :         map->new_tablespace_suffix = "/base";
     190             :     }
     191             :     else
     192             :     {
     193           0 :         map->new_tablespace = new_rel->tablespace;
     194           0 :         map->new_tablespace_suffix = new_cluster.tablespace_suffix;
     195             :     }
     196             : 
     197             :     /* DB oid and relfilenumbers are preserved between old and new cluster */
     198        2736 :     map->db_oid = old_db->db_oid;
     199        2736 :     map->relfilenumber = old_rel->relfilenumber;
     200             : 
     201             :     /* used only for logging and error reporting, old/new are identical */
     202        2736 :     map->nspname = old_rel->nspname;
     203        2736 :     map->relname = old_rel->relname;
     204        2736 : }
     205             : 
     206             : 
     207             : /*
     208             :  * Complain about a relation we couldn't match to the other database,
     209             :  * identifying it as best we can.
     210             :  */
     211             : static void
     212           0 : report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db)
     213             : {
     214           0 :     Oid         reloid = rel->reloid;    /* we might change rel below */
     215             :     char        reldesc[1000];
     216             :     int         i;
     217             : 
     218           0 :     snprintf(reldesc, sizeof(reldesc), "\"%s.%s\"",
     219             :              rel->nspname, rel->relname);
     220           0 :     if (rel->indtable)
     221             :     {
     222           0 :         for (i = 0; i < db->rel_arr.nrels; i++)
     223             :         {
     224           0 :             const RelInfo *hrel = &db->rel_arr.rels[i];
     225             : 
     226           0 :             if (hrel->reloid == rel->indtable)
     227             :             {
     228           0 :                 snprintf(reldesc + strlen(reldesc),
     229           0 :                          sizeof(reldesc) - strlen(reldesc),
     230           0 :                          _(" which is an index on \"%s.%s\""),
     231             :                          hrel->nspname, hrel->relname);
     232             :                 /* Shift attention to index's table for toast check */
     233           0 :                 rel = hrel;
     234           0 :                 break;
     235             :             }
     236             :         }
     237           0 :         if (i >= db->rel_arr.nrels)
     238           0 :             snprintf(reldesc + strlen(reldesc),
     239           0 :                      sizeof(reldesc) - strlen(reldesc),
     240           0 :                      _(" which is an index on OID %u"), rel->indtable);
     241             :     }
     242           0 :     if (rel->toastheap)
     243             :     {
     244           0 :         for (i = 0; i < db->rel_arr.nrels; i++)
     245             :         {
     246           0 :             const RelInfo *brel = &db->rel_arr.rels[i];
     247             : 
     248           0 :             if (brel->reloid == rel->toastheap)
     249             :             {
     250           0 :                 snprintf(reldesc + strlen(reldesc),
     251           0 :                          sizeof(reldesc) - strlen(reldesc),
     252           0 :                          _(" which is the TOAST table for \"%s.%s\""),
     253             :                          brel->nspname, brel->relname);
     254           0 :                 break;
     255             :             }
     256             :         }
     257           0 :         if (i >= db->rel_arr.nrels)
     258           0 :             snprintf(reldesc + strlen(reldesc),
     259           0 :                      sizeof(reldesc) - strlen(reldesc),
     260           0 :                      _(" which is the TOAST table for OID %u"), rel->toastheap);
     261             :     }
     262             : 
     263           0 :     if (is_new_db)
     264           0 :         pg_log(PG_WARNING, "No match found in old cluster for new relation with OID %u in database \"%s\": %s",
     265             :                reloid, db->db_name, reldesc);
     266             :     else
     267           0 :         pg_log(PG_WARNING, "No match found in new cluster for old relation with OID %u in database \"%s\": %s",
     268             :                reloid, db->db_name, reldesc);
     269           0 : }
     270             : 
     271             : /*
     272             :  * get_db_rel_and_slot_infos()
     273             :  *
     274             :  * higher level routine to generate dbinfos for the database running
     275             :  * on the given "port". Assumes that server is already running.
     276             :  *
     277             :  * live_check would be used only when the target is the old cluster.
     278             :  */
     279             : void
     280          36 : get_db_rel_and_slot_infos(ClusterInfo *cluster, bool live_check)
     281             : {
     282             :     int         dbnum;
     283             : 
     284          36 :     if (cluster->dbarr.dbs != NULL)
     285           6 :         free_db_and_rel_infos(&cluster->dbarr);
     286             : 
     287          36 :     get_template0_info(cluster);
     288          36 :     get_db_infos(cluster);
     289             : 
     290         140 :     for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
     291             :     {
     292         106 :         DbInfo     *pDbInfo = &cluster->dbarr.dbs[dbnum];
     293             : 
     294         106 :         get_rel_infos(cluster, pDbInfo);
     295             : 
     296             :         /*
     297             :          * Retrieve the logical replication slots infos and the subscriptions
     298             :          * count for the old cluster.
     299             :          */
     300         104 :         if (cluster == &old_cluster)
     301             :         {
     302          60 :             get_old_cluster_logical_slot_infos(pDbInfo, live_check);
     303          60 :             get_db_subscription_count(pDbInfo);
     304             :         }
     305             :     }
     306             : 
     307          34 :     if (cluster == &old_cluster)
     308          16 :         pg_log(PG_VERBOSE, "\nsource databases:");
     309             :     else
     310          18 :         pg_log(PG_VERBOSE, "\ntarget databases:");
     311             : 
     312          34 :     if (log_opts.verbose)
     313           0 :         print_db_infos(&cluster->dbarr);
     314          34 : }
     315             : 
     316             : 
     317             : /*
     318             :  * Get information about template0, which will be copied from the old cluster
     319             :  * to the new cluster.
     320             :  */
     321             : static void
     322          36 : get_template0_info(ClusterInfo *cluster)
     323             : {
     324          36 :     PGconn     *conn = connectToServer(cluster, "template1");
     325             :     DbLocaleInfo *locale;
     326             :     PGresult   *dbres;
     327             :     int         i_datencoding;
     328             :     int         i_datlocprovider;
     329             :     int         i_datcollate;
     330             :     int         i_datctype;
     331             :     int         i_datlocale;
     332             : 
     333          36 :     if (GET_MAJOR_VERSION(cluster->major_version) >= 1700)
     334          36 :         dbres = executeQueryOrDie(conn,
     335             :                                   "SELECT encoding, datlocprovider, "
     336             :                                   "       datcollate, datctype, datlocale "
     337             :                                   "FROM    pg_catalog.pg_database "
     338             :                                   "WHERE datname='template0'");
     339           0 :     else if (GET_MAJOR_VERSION(cluster->major_version) >= 1500)
     340           0 :         dbres = executeQueryOrDie(conn,
     341             :                                   "SELECT encoding, datlocprovider, "
     342             :                                   "       datcollate, datctype, daticulocale AS datlocale "
     343             :                                   "FROM    pg_catalog.pg_database "
     344             :                                   "WHERE datname='template0'");
     345             :     else
     346           0 :         dbres = executeQueryOrDie(conn,
     347             :                                   "SELECT encoding, 'c' AS datlocprovider, "
     348             :                                   "       datcollate, datctype, NULL AS datlocale "
     349             :                                   "FROM    pg_catalog.pg_database "
     350             :                                   "WHERE datname='template0'");
     351             : 
     352             : 
     353          36 :     if (PQntuples(dbres) != 1)
     354           0 :         pg_fatal("template0 not found");
     355             : 
     356          36 :     locale = pg_malloc(sizeof(DbLocaleInfo));
     357             : 
     358          36 :     i_datencoding = PQfnumber(dbres, "encoding");
     359          36 :     i_datlocprovider = PQfnumber(dbres, "datlocprovider");
     360          36 :     i_datcollate = PQfnumber(dbres, "datcollate");
     361          36 :     i_datctype = PQfnumber(dbres, "datctype");
     362          36 :     i_datlocale = PQfnumber(dbres, "datlocale");
     363             : 
     364          36 :     locale->db_encoding = atoi(PQgetvalue(dbres, 0, i_datencoding));
     365          36 :     locale->db_collprovider = PQgetvalue(dbres, 0, i_datlocprovider)[0];
     366          36 :     locale->db_collate = pg_strdup(PQgetvalue(dbres, 0, i_datcollate));
     367          36 :     locale->db_ctype = pg_strdup(PQgetvalue(dbres, 0, i_datctype));
     368          36 :     if (PQgetisnull(dbres, 0, i_datlocale))
     369          28 :         locale->db_locale = NULL;
     370             :     else
     371           8 :         locale->db_locale = pg_strdup(PQgetvalue(dbres, 0, i_datlocale));
     372             : 
     373          36 :     cluster->template0 = locale;
     374             : 
     375          36 :     PQclear(dbres);
     376          36 :     PQfinish(conn);
     377          36 : }
     378             : 
     379             : 
     380             : /*
     381             :  * get_db_infos()
     382             :  *
     383             :  * Scans pg_database system catalog and populates all user
     384             :  * databases.
     385             :  */
     386             : static void
     387          36 : get_db_infos(ClusterInfo *cluster)
     388             : {
     389          36 :     PGconn     *conn = connectToServer(cluster, "template1");
     390             :     PGresult   *res;
     391             :     int         ntups;
     392             :     int         tupnum;
     393             :     DbInfo     *dbinfos;
     394             :     int         i_datname,
     395             :                 i_oid,
     396             :                 i_spclocation;
     397             :     char        query[QUERY_ALLOC];
     398             : 
     399          36 :     snprintf(query, sizeof(query),
     400             :              "SELECT d.oid, d.datname, d.encoding, d.datcollate, d.datctype, ");
     401          36 :     if (GET_MAJOR_VERSION(cluster->major_version) >= 1700)
     402          36 :         snprintf(query + strlen(query), sizeof(query) - strlen(query),
     403             :                  "datlocprovider, datlocale, ");
     404           0 :     else if (GET_MAJOR_VERSION(cluster->major_version) >= 1500)
     405           0 :         snprintf(query + strlen(query), sizeof(query) - strlen(query),
     406             :                  "datlocprovider, daticulocale AS datlocale, ");
     407             :     else
     408           0 :         snprintf(query + strlen(query), sizeof(query) - strlen(query),
     409             :                  "'c' AS datlocprovider, NULL AS datlocale, ");
     410          36 :     snprintf(query + strlen(query), sizeof(query) - strlen(query),
     411             :              "pg_catalog.pg_tablespace_location(t.oid) AS spclocation "
     412             :              "FROM pg_catalog.pg_database d "
     413             :              " LEFT OUTER JOIN pg_catalog.pg_tablespace t "
     414             :              " ON d.dattablespace = t.oid "
     415             :              "WHERE d.datallowconn = true "
     416             :              "ORDER BY 1");
     417             : 
     418          36 :     res = executeQueryOrDie(conn, "%s", query);
     419             : 
     420          36 :     i_oid = PQfnumber(res, "oid");
     421          36 :     i_datname = PQfnumber(res, "datname");
     422          36 :     i_spclocation = PQfnumber(res, "spclocation");
     423             : 
     424          36 :     ntups = PQntuples(res);
     425          36 :     dbinfos = (DbInfo *) pg_malloc0(sizeof(DbInfo) * ntups);
     426             : 
     427         142 :     for (tupnum = 0; tupnum < ntups; tupnum++)
     428             :     {
     429         106 :         dbinfos[tupnum].db_oid = atooid(PQgetvalue(res, tupnum, i_oid));
     430         106 :         dbinfos[tupnum].db_name = pg_strdup(PQgetvalue(res, tupnum, i_datname));
     431         106 :         snprintf(dbinfos[tupnum].db_tablespace, sizeof(dbinfos[tupnum].db_tablespace), "%s",
     432             :                  PQgetvalue(res, tupnum, i_spclocation));
     433             :     }
     434          36 :     PQclear(res);
     435             : 
     436          36 :     PQfinish(conn);
     437             : 
     438          36 :     cluster->dbarr.dbs = dbinfos;
     439          36 :     cluster->dbarr.ndbs = ntups;
     440          36 : }
     441             : 
     442             : 
     443             : /*
     444             :  * get_rel_infos()
     445             :  *
     446             :  * gets the relinfos for all the user tables and indexes of the database
     447             :  * referred to by "dbinfo".
     448             :  *
     449             :  * Note: the resulting RelInfo array is assumed to be sorted by OID.
     450             :  * This allows later processing to match up old and new databases efficiently.
     451             :  */
     452             : static void
     453         106 : get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
     454             : {
     455         106 :     PGconn     *conn = connectToServer(cluster,
     456         106 :                                        dbinfo->db_name);
     457             :     PGresult   *res;
     458             :     RelInfo    *relinfos;
     459             :     int         ntups;
     460             :     int         relnum;
     461         104 :     int         num_rels = 0;
     462         104 :     char       *nspname = NULL;
     463         104 :     char       *relname = NULL;
     464         104 :     char       *tablespace = NULL;
     465             :     int         i_spclocation,
     466             :                 i_nspname,
     467             :                 i_relname,
     468             :                 i_reloid,
     469             :                 i_indtable,
     470             :                 i_toastheap,
     471             :                 i_relfilenumber,
     472             :                 i_reltablespace;
     473             :     char        query[QUERY_ALLOC];
     474         104 :     char       *last_namespace = NULL,
     475         104 :                *last_tablespace = NULL;
     476             : 
     477         104 :     query[0] = '\0';            /* initialize query string to empty */
     478             : 
     479             :     /*
     480             :      * Create a CTE that collects OIDs of regular user tables and matviews,
     481             :      * but excluding toast tables and indexes.  We assume that relations with
     482             :      * OIDs >= FirstNormalObjectId belong to the user.  (That's probably
     483             :      * redundant with the namespace-name exclusions, but let's be safe.)
     484             :      *
     485             :      * pg_largeobject contains user data that does not appear in pg_dump
     486             :      * output, so we have to copy that system table.  It's easiest to do that
     487             :      * by treating it as a user table.
     488             :      */
     489         104 :     snprintf(query + strlen(query), sizeof(query) - strlen(query),
     490             :              "WITH regular_heap (reloid, indtable, toastheap) AS ( "
     491             :              "  SELECT c.oid, 0::oid, 0::oid "
     492             :              "  FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
     493             :              "         ON c.relnamespace = n.oid "
     494             :              "  WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
     495             :              CppAsString2(RELKIND_MATVIEW) ") AND "
     496             :     /* exclude possible orphaned temp tables */
     497             :              "    ((n.nspname !~ '^pg_temp_' AND "
     498             :              "      n.nspname !~ '^pg_toast_temp_' AND "
     499             :              "      n.nspname NOT IN ('pg_catalog', 'information_schema', "
     500             :              "                        'binary_upgrade', 'pg_toast') AND "
     501             :              "      c.oid >= %u::pg_catalog.oid) OR "
     502             :              "     (n.nspname = 'pg_catalog' AND "
     503             :              "      relname IN ('pg_largeobject') ))), ",
     504             :              FirstNormalObjectId);
     505             : 
     506             :     /*
     507             :      * Add a CTE that collects OIDs of toast tables belonging to the tables
     508             :      * selected by the regular_heap CTE.  (We have to do this separately
     509             :      * because the namespace-name rules above don't work for toast tables.)
     510             :      */
     511         104 :     snprintf(query + strlen(query), sizeof(query) - strlen(query),
     512             :              "  toast_heap (reloid, indtable, toastheap) AS ( "
     513             :              "  SELECT c.reltoastrelid, 0::oid, c.oid "
     514             :              "  FROM regular_heap JOIN pg_catalog.pg_class c "
     515             :              "      ON regular_heap.reloid = c.oid "
     516             :              "  WHERE c.reltoastrelid != 0), ");
     517             : 
     518             :     /*
     519             :      * Add a CTE that collects OIDs of all valid indexes on the previously
     520             :      * selected tables.  We can ignore invalid indexes since pg_dump does.
     521             :      * Testing indisready is necessary in 9.2, and harmless in earlier/later
     522             :      * versions.
     523             :      */
     524         104 :     snprintf(query + strlen(query), sizeof(query) - strlen(query),
     525             :              "  all_index (reloid, indtable, toastheap) AS ( "
     526             :              "  SELECT indexrelid, indrelid, 0::oid "
     527             :              "  FROM pg_catalog.pg_index "
     528             :              "  WHERE indisvalid AND indisready "
     529             :              "    AND indrelid IN "
     530             :              "        (SELECT reloid FROM regular_heap "
     531             :              "         UNION ALL "
     532             :              "         SELECT reloid FROM toast_heap)) ");
     533             : 
     534             :     /*
     535             :      * And now we can write the query that retrieves the data we want for each
     536             :      * heap and index relation.  Make sure result is sorted by OID.
     537             :      */
     538         104 :     snprintf(query + strlen(query), sizeof(query) - strlen(query),
     539             :              "SELECT all_rels.*, n.nspname, c.relname, "
     540             :              "  c.relfilenode, c.reltablespace, "
     541             :              "  pg_catalog.pg_tablespace_location(t.oid) AS spclocation "
     542             :              "FROM (SELECT * FROM regular_heap "
     543             :              "      UNION ALL "
     544             :              "      SELECT * FROM toast_heap "
     545             :              "      UNION ALL "
     546             :              "      SELECT * FROM all_index) all_rels "
     547             :              "  JOIN pg_catalog.pg_class c "
     548             :              "      ON all_rels.reloid = c.oid "
     549             :              "  JOIN pg_catalog.pg_namespace n "
     550             :              "     ON c.relnamespace = n.oid "
     551             :              "  LEFT OUTER JOIN pg_catalog.pg_tablespace t "
     552             :              "     ON c.reltablespace = t.oid "
     553             :              "ORDER BY 1;");
     554             : 
     555         104 :     res = executeQueryOrDie(conn, "%s", query);
     556             : 
     557         104 :     ntups = PQntuples(res);
     558             : 
     559         104 :     relinfos = (RelInfo *) pg_malloc(sizeof(RelInfo) * ntups);
     560             : 
     561         104 :     i_reloid = PQfnumber(res, "reloid");
     562         104 :     i_indtable = PQfnumber(res, "indtable");
     563         104 :     i_toastheap = PQfnumber(res, "toastheap");
     564         104 :     i_nspname = PQfnumber(res, "nspname");
     565         104 :     i_relname = PQfnumber(res, "relname");
     566         104 :     i_relfilenumber = PQfnumber(res, "relfilenode");
     567         104 :     i_reltablespace = PQfnumber(res, "reltablespace");
     568         104 :     i_spclocation = PQfnumber(res, "spclocation");
     569             : 
     570       11090 :     for (relnum = 0; relnum < ntups; relnum++)
     571             :     {
     572       10986 :         RelInfo    *curr = &relinfos[num_rels++];
     573             : 
     574       10986 :         curr->reloid = atooid(PQgetvalue(res, relnum, i_reloid));
     575       10986 :         curr->indtable = atooid(PQgetvalue(res, relnum, i_indtable));
     576       10986 :         curr->toastheap = atooid(PQgetvalue(res, relnum, i_toastheap));
     577             : 
     578       10986 :         nspname = PQgetvalue(res, relnum, i_nspname);
     579       10986 :         curr->nsp_alloc = false;
     580             : 
     581             :         /*
     582             :          * Many of the namespace and tablespace strings are identical, so we
     583             :          * try to reuse the allocated string pointers where possible to reduce
     584             :          * memory consumption.
     585             :          */
     586             :         /* Can we reuse the previous string allocation? */
     587       10986 :         if (last_namespace && strcmp(nspname, last_namespace) == 0)
     588        6862 :             curr->nspname = last_namespace;
     589             :         else
     590             :         {
     591        4124 :             last_namespace = curr->nspname = pg_strdup(nspname);
     592        4124 :             curr->nsp_alloc = true;
     593             :         }
     594             : 
     595       10986 :         relname = PQgetvalue(res, relnum, i_relname);
     596       10986 :         curr->relname = pg_strdup(relname);
     597             : 
     598       10986 :         curr->relfilenumber = atooid(PQgetvalue(res, relnum, i_relfilenumber));
     599       10986 :         curr->tblsp_alloc = false;
     600             : 
     601             :         /* Is the tablespace oid non-default? */
     602       10986 :         if (atooid(PQgetvalue(res, relnum, i_reltablespace)) != 0)
     603             :         {
     604             :             /*
     605             :              * The tablespace location might be "", meaning the cluster
     606             :              * default location, i.e. pg_default or pg_global.
     607             :              */
     608           0 :             tablespace = PQgetvalue(res, relnum, i_spclocation);
     609             : 
     610             :             /* Can we reuse the previous string allocation? */
     611           0 :             if (last_tablespace && strcmp(tablespace, last_tablespace) == 0)
     612           0 :                 curr->tablespace = last_tablespace;
     613             :             else
     614             :             {
     615           0 :                 last_tablespace = curr->tablespace = pg_strdup(tablespace);
     616           0 :                 curr->tblsp_alloc = true;
     617             :             }
     618             :         }
     619             :         else
     620             :             /* A zero reltablespace oid indicates the database tablespace. */
     621       10986 :             curr->tablespace = dbinfo->db_tablespace;
     622             :     }
     623         104 :     PQclear(res);
     624             : 
     625         104 :     PQfinish(conn);
     626             : 
     627         104 :     dbinfo->rel_arr.rels = relinfos;
     628         104 :     dbinfo->rel_arr.nrels = num_rels;
     629         104 : }
     630             : 
     631             : /*
     632             :  * get_old_cluster_logical_slot_infos()
     633             :  *
     634             :  * Gets the LogicalSlotInfos for all the logical replication slots of the
     635             :  * database referred to by "dbinfo". The status of each logical slot is gotten
     636             :  * here, but they are used at the checking phase. See
     637             :  * check_old_cluster_for_valid_slots().
     638             :  *
     639             :  * Note: This function will not do anything if the old cluster is pre-PG17.
     640             :  * This is because before that the logical slots are not saved at shutdown, so
     641             :  * there is no guarantee that the latest confirmed_flush_lsn is saved to disk
     642             :  * which can lead to data loss. It is still not guaranteed for manually created
     643             :  * slots in PG17, so subsequent checks done in
     644             :  * check_old_cluster_for_valid_slots() would raise a FATAL error if such slots
     645             :  * are included.
     646             :  */
     647             : static void
     648          60 : get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
     649             : {
     650             :     PGconn     *conn;
     651             :     PGresult   *res;
     652          60 :     LogicalSlotInfo *slotinfos = NULL;
     653             :     int         num_slots;
     654             : 
     655             :     /* Logical slots can be migrated since PG17. */
     656          60 :     if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1600)
     657           0 :         return;
     658             : 
     659          60 :     conn = connectToServer(&old_cluster, dbinfo->db_name);
     660             : 
     661             :     /*
     662             :      * Fetch the logical replication slot information. The check whether the
     663             :      * slot is considered caught up is done by an upgrade function. This
     664             :      * regards the slot as caught up if we don't find any decodable changes.
     665             :      * See binary_upgrade_logical_slot_has_caught_up().
     666             :      *
     667             :      * Note that we can't ensure whether the slot is caught up during
     668             :      * live_check as the new WAL records could be generated.
     669             :      *
     670             :      * We intentionally skip checking the WALs for invalidated slots as the
     671             :      * corresponding WALs could have been removed for such slots.
     672             :      *
     673             :      * The temporary slots are explicitly ignored while checking because such
     674             :      * slots cannot exist after the upgrade. During the upgrade, clusters are
     675             :      * started and stopped several times causing any temporary slots to be
     676             :      * removed.
     677             :      */
     678          60 :     res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
     679             :                             "%s as caught_up, invalidation_reason IS NOT NULL as invalid "
     680             :                             "FROM pg_catalog.pg_replication_slots "
     681             :                             "WHERE slot_type = 'logical' AND "
     682             :                             "database = current_database() AND "
     683             :                             "temporary IS FALSE;",
     684             :                             live_check ? "FALSE" :
     685             :                             "(CASE WHEN invalidation_reason IS NOT NULL THEN FALSE "
     686             :                             "ELSE (SELECT pg_catalog.binary_upgrade_logical_slot_has_caught_up(slot_name)) "
     687             :                             "END)");
     688             : 
     689          60 :     num_slots = PQntuples(res);
     690             : 
     691          60 :     if (num_slots)
     692             :     {
     693             :         int         i_slotname;
     694             :         int         i_plugin;
     695             :         int         i_twophase;
     696             :         int         i_failover;
     697             :         int         i_caught_up;
     698             :         int         i_invalid;
     699             : 
     700           6 :         slotinfos = (LogicalSlotInfo *) pg_malloc(sizeof(LogicalSlotInfo) * num_slots);
     701             : 
     702           6 :         i_slotname = PQfnumber(res, "slot_name");
     703           6 :         i_plugin = PQfnumber(res, "plugin");
     704           6 :         i_twophase = PQfnumber(res, "two_phase");
     705           6 :         i_failover = PQfnumber(res, "failover");
     706           6 :         i_caught_up = PQfnumber(res, "caught_up");
     707           6 :         i_invalid = PQfnumber(res, "invalid");
     708             : 
     709          16 :         for (int slotnum = 0; slotnum < num_slots; slotnum++)
     710             :         {
     711          10 :             LogicalSlotInfo *curr = &slotinfos[slotnum];
     712             : 
     713          10 :             curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
     714          10 :             curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
     715          10 :             curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
     716          10 :             curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
     717          10 :             curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
     718          10 :             curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
     719             :         }
     720             :     }
     721             : 
     722          60 :     PQclear(res);
     723          60 :     PQfinish(conn);
     724             : 
     725          60 :     dbinfo->slot_arr.slots = slotinfos;
     726          60 :     dbinfo->slot_arr.nslots = num_slots;
     727             : }
     728             : 
     729             : 
     730             : /*
     731             :  * count_old_cluster_logical_slots()
     732             :  *
     733             :  * Returns the number of logical replication slots for all databases.
     734             :  *
     735             :  * Note: this function always returns 0 if the old_cluster is PG16 and prior
     736             :  * because we gather slot information only for cluster versions greater than or
     737             :  * equal to PG17. See get_old_cluster_logical_slot_infos().
     738             :  */
     739             : int
     740          34 : count_old_cluster_logical_slots(void)
     741             : {
     742          34 :     int         slot_count = 0;
     743             : 
     744         142 :     for (int dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
     745         108 :         slot_count += old_cluster.dbarr.dbs[dbnum].slot_arr.nslots;
     746             : 
     747          34 :     return slot_count;
     748             : }
     749             : 
     750             : /*
     751             :  * get_db_subscription_count()
     752             :  *
     753             :  * Gets the number of subscriptions in the database referred to by "dbinfo".
     754             :  *
     755             :  * Note: This function will not do anything if the old cluster is pre-PG17.
     756             :  * This is because before that the logical slots are not upgraded, so we will
     757             :  * not be able to upgrade the logical replication clusters completely.
     758             :  */
     759             : static void
     760          60 : get_db_subscription_count(DbInfo *dbinfo)
     761             : {
     762             :     PGconn     *conn;
     763             :     PGresult   *res;
     764             : 
     765             :     /* Subscriptions can be migrated since PG17. */
     766          60 :     if (GET_MAJOR_VERSION(old_cluster.major_version) < 1700)
     767           0 :         return;
     768             : 
     769          60 :     conn = connectToServer(&old_cluster, dbinfo->db_name);
     770          60 :     res = executeQueryOrDie(conn, "SELECT count(*) "
     771             :                             "FROM pg_catalog.pg_subscription WHERE subdbid = %u",
     772             :                             dbinfo->db_oid);
     773          60 :     dbinfo->nsubs = atoi(PQgetvalue(res, 0, 0));
     774             : 
     775          60 :     PQclear(res);
     776          60 :     PQfinish(conn);
     777             : }
     778             : 
     779             : /*
     780             :  * count_old_cluster_subscriptions()
     781             :  *
     782             :  * Returns the number of subscriptions for all databases.
     783             :  *
     784             :  * Note: this function always returns 0 if the old_cluster is PG16 and prior
     785             :  * because we gather subscriptions only for cluster versions greater than or
     786             :  * equal to PG17. See get_db_subscription_count().
     787             :  */
     788             : int
     789          10 : count_old_cluster_subscriptions(void)
     790             : {
     791          10 :     int         nsubs = 0;
     792             : 
     793          46 :     for (int dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++)
     794          36 :         nsubs += old_cluster.dbarr.dbs[dbnum].nsubs;
     795             : 
     796          10 :     return nsubs;
     797             : }
     798             : 
     799             : static void
     800           6 : free_db_and_rel_infos(DbInfoArr *db_arr)
     801             : {
     802             :     int         dbnum;
     803             : 
     804          18 :     for (dbnum = 0; dbnum < db_arr->ndbs; dbnum++)
     805             :     {
     806          12 :         free_rel_infos(&db_arr->dbs[dbnum].rel_arr);
     807          12 :         pg_free(db_arr->dbs[dbnum].db_name);
     808             :     }
     809           6 :     pg_free(db_arr->dbs);
     810           6 :     db_arr->dbs = NULL;
     811           6 :     db_arr->ndbs = 0;
     812           6 : }
     813             : 
     814             : 
     815             : static void
     816          12 : free_rel_infos(RelInfoArr *rel_arr)
     817             : {
     818             :     int         relnum;
     819             : 
     820          36 :     for (relnum = 0; relnum < rel_arr->nrels; relnum++)
     821             :     {
     822          24 :         if (rel_arr->rels[relnum].nsp_alloc)
     823          12 :             pg_free(rel_arr->rels[relnum].nspname);
     824          24 :         pg_free(rel_arr->rels[relnum].relname);
     825          24 :         if (rel_arr->rels[relnum].tblsp_alloc)
     826           0 :             pg_free(rel_arr->rels[relnum].tablespace);
     827             :     }
     828          12 :     pg_free(rel_arr->rels);
     829          12 :     rel_arr->nrels = 0;
     830          12 : }
     831             : 
     832             : 
     833             : static void
     834           0 : print_db_infos(DbInfoArr *db_arr)
     835             : {
     836             :     int         dbnum;
     837             : 
     838           0 :     for (dbnum = 0; dbnum < db_arr->ndbs; dbnum++)
     839             :     {
     840           0 :         DbInfo     *pDbInfo = &db_arr->dbs[dbnum];
     841             : 
     842           0 :         pg_log(PG_VERBOSE, "Database: \"%s\"", pDbInfo->db_name);
     843           0 :         print_rel_infos(&pDbInfo->rel_arr);
     844           0 :         print_slot_infos(&pDbInfo->slot_arr);
     845             :     }
     846           0 : }
     847             : 
     848             : 
     849             : static void
     850           0 : print_rel_infos(RelInfoArr *rel_arr)
     851             : {
     852             :     int         relnum;
     853             : 
     854           0 :     for (relnum = 0; relnum < rel_arr->nrels; relnum++)
     855           0 :         pg_log(PG_VERBOSE, "relname: \"%s.%s\", reloid: %u, reltblspace: \"%s\"",
     856           0 :                rel_arr->rels[relnum].nspname,
     857           0 :                rel_arr->rels[relnum].relname,
     858           0 :                rel_arr->rels[relnum].reloid,
     859           0 :                rel_arr->rels[relnum].tablespace);
     860           0 : }
     861             : 
     862             : static void
     863           0 : print_slot_infos(LogicalSlotInfoArr *slot_arr)
     864             : {
     865             :     /* Quick return if there are no logical slots. */
     866           0 :     if (slot_arr->nslots == 0)
     867           0 :         return;
     868             : 
     869           0 :     pg_log(PG_VERBOSE, "Logical replication slots within the database:");
     870             : 
     871           0 :     for (int slotnum = 0; slotnum < slot_arr->nslots; slotnum++)
     872             :     {
     873           0 :         LogicalSlotInfo *slot_info = &slot_arr->slots[slotnum];
     874             : 
     875           0 :         pg_log(PG_VERBOSE, "slot_name: \"%s\", plugin: \"%s\", two_phase: %s",
     876             :                slot_info->slotname,
     877             :                slot_info->plugin,
     878           0 :                slot_info->two_phase ? "true" : "false");
     879             :     }
     880             : }

Generated by: LCOV version 1.14