summaryrefslogtreecommitdiffstats
path: root/src/bin/pg_upgrade/info.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/pg_upgrade/info.c')
-rw-r--r--src/bin/pg_upgrade/info.c622
1 files changed, 622 insertions, 0 deletions
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
new file mode 100644
index 0000000..a9ce740
--- /dev/null
+++ b/src/bin/pg_upgrade/info.c
@@ -0,0 +1,622 @@
+/*
+ * info.c
+ *
+ * information support functions
+ *
+ * Copyright (c) 2010-2022, PostgreSQL Global Development Group
+ * src/bin/pg_upgrade/info.c
+ */
+
+#include "postgres_fe.h"
+
+#include "access/transam.h"
+#include "catalog/pg_class_d.h"
+#include "pg_upgrade.h"
+
+static void create_rel_filename_map(const char *old_data, const char *new_data,
+ const DbInfo *old_db, const DbInfo *new_db,
+ const RelInfo *old_rel, const RelInfo *new_rel,
+ FileNameMap *map);
+static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
+ bool is_new_db);
+static void free_db_and_rel_infos(DbInfoArr *db_arr);
+static void get_db_infos(ClusterInfo *cluster);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void free_rel_infos(RelInfoArr *rel_arr);
+static void print_db_infos(DbInfoArr *dbinfo);
+static void print_rel_infos(RelInfoArr *rel_arr);
+
+
+/*
+ * gen_db_file_maps()
+ *
+ * generates a database mapping from "old_db" to "new_db".
+ *
+ * Returns a malloc'ed array of mappings. The length of the array
+ * is returned into *nmaps.
+ */
+FileNameMap *
+gen_db_file_maps(DbInfo *old_db, DbInfo *new_db,
+ int *nmaps,
+ const char *old_pgdata, const char *new_pgdata)
+{
+ FileNameMap *maps;
+ int old_relnum,
+ new_relnum;
+ int num_maps = 0;
+ bool all_matched = true;
+
+ /* There will certainly not be more mappings than there are old rels */
+ maps = (FileNameMap *) pg_malloc(sizeof(FileNameMap) *
+ old_db->rel_arr.nrels);
+
+ /*
+ * Each of the RelInfo arrays should be sorted by OID. Scan through them
+ * and match them up. If we fail to match everything, we'll abort, but
+ * first print as much info as we can about mismatches.
+ */
+ old_relnum = new_relnum = 0;
+ while (old_relnum < old_db->rel_arr.nrels ||
+ new_relnum < new_db->rel_arr.nrels)
+ {
+ RelInfo *old_rel = (old_relnum < old_db->rel_arr.nrels) ?
+ &old_db->rel_arr.rels[old_relnum] : NULL;
+ RelInfo *new_rel = (new_relnum < new_db->rel_arr.nrels) ?
+ &new_db->rel_arr.rels[new_relnum] : NULL;
+
+ /* handle running off one array before the other */
+ if (!new_rel)
+ {
+ /*
+ * old_rel is unmatched. This should never happen, because we
+ * force new rels to have TOAST tables if the old one did.
+ */
+ report_unmatched_relation(old_rel, old_db, false);
+ all_matched = false;
+ old_relnum++;
+ continue;
+ }
+ if (!old_rel)
+ {
+ /*
+ * new_rel is unmatched. This shouldn't really happen either, but
+ * if it's a TOAST table, we can ignore it and continue
+ * processing, assuming that the new server made a TOAST table
+ * that wasn't needed.
+ */
+ if (strcmp(new_rel->nspname, "pg_toast") != 0)
+ {
+ report_unmatched_relation(new_rel, new_db, true);
+ all_matched = false;
+ }
+ new_relnum++;
+ continue;
+ }
+
+ /* check for mismatched OID */
+ if (old_rel->reloid < new_rel->reloid)
+ {
+ /* old_rel is unmatched, see comment above */
+ report_unmatched_relation(old_rel, old_db, false);
+ all_matched = false;
+ old_relnum++;
+ continue;
+ }
+ else if (old_rel->reloid > new_rel->reloid)
+ {
+ /* new_rel is unmatched, see comment above */
+ if (strcmp(new_rel->nspname, "pg_toast") != 0)
+ {
+ report_unmatched_relation(new_rel, new_db, true);
+ all_matched = false;
+ }
+ new_relnum++;
+ continue;
+ }
+
+ /*
+ * Verify that rels of same OID have same name. The namespace name
+ * should always match, but the relname might not match for TOAST
+ * tables (and, therefore, their indexes).
+ */
+ if (strcmp(old_rel->nspname, new_rel->nspname) != 0 ||
+ strcmp(old_rel->relname, new_rel->relname) != 0)
+ {
+ pg_log(PG_WARNING, "Relation names for OID %u in database \"%s\" do not match: "
+ "old name \"%s.%s\", new name \"%s.%s\"\n",
+ old_rel->reloid, old_db->db_name,
+ old_rel->nspname, old_rel->relname,
+ new_rel->nspname, new_rel->relname);
+ all_matched = false;
+ old_relnum++;
+ new_relnum++;
+ continue;
+ }
+
+ /* OK, create a mapping entry */
+ create_rel_filename_map(old_pgdata, new_pgdata, old_db, new_db,
+ old_rel, new_rel, maps + num_maps);
+ num_maps++;
+ old_relnum++;
+ new_relnum++;
+ }
+
+ if (!all_matched)
+ pg_fatal("Failed to match up old and new tables in database \"%s\"\n",
+ old_db->db_name);
+
+ *nmaps = num_maps;
+ return maps;
+}
+
+
+/*
+ * create_rel_filename_map()
+ *
+ * fills a file node map structure and returns it in "map".
+ */
+static void
+create_rel_filename_map(const char *old_data, const char *new_data,
+ const DbInfo *old_db, const DbInfo *new_db,
+ const RelInfo *old_rel, const RelInfo *new_rel,
+ FileNameMap *map)
+{
+ /* In case old/new tablespaces don't match, do them separately. */
+ if (strlen(old_rel->tablespace) == 0)
+ {
+ /*
+ * relation belongs to the default tablespace, hence relfiles should
+ * exist in the data directories.
+ */
+ map->old_tablespace = old_data;
+ map->old_tablespace_suffix = "/base";
+ }
+ else
+ {
+ /* relation belongs to a tablespace, so use the tablespace location */
+ map->old_tablespace = old_rel->tablespace;
+ map->old_tablespace_suffix = old_cluster.tablespace_suffix;
+ }
+
+ /* Do the same for new tablespaces */
+ if (strlen(new_rel->tablespace) == 0)
+ {
+ map->new_tablespace = new_data;
+ map->new_tablespace_suffix = "/base";
+ }
+ else
+ {
+ map->new_tablespace = new_rel->tablespace;
+ map->new_tablespace_suffix = new_cluster.tablespace_suffix;
+ }
+
+ /* DB oid and relfilenodes are preserved between old and new cluster */
+ map->db_oid = old_db->db_oid;
+ map->relfilenode = old_rel->relfilenode;
+
+ /* used only for logging and error reporting, old/new are identical */
+ map->nspname = old_rel->nspname;
+ map->relname = old_rel->relname;
+}
+
+
+/*
+ * Complain about a relation we couldn't match to the other database,
+ * identifying it as best we can.
+ */
+static void
+report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db)
+{
+ Oid reloid = rel->reloid; /* we might change rel below */
+ char reldesc[1000];
+ int i;
+
+ snprintf(reldesc, sizeof(reldesc), "\"%s.%s\"",
+ rel->nspname, rel->relname);
+ if (rel->indtable)
+ {
+ for (i = 0; i < db->rel_arr.nrels; i++)
+ {
+ const RelInfo *hrel = &db->rel_arr.rels[i];
+
+ if (hrel->reloid == rel->indtable)
+ {
+ snprintf(reldesc + strlen(reldesc),
+ sizeof(reldesc) - strlen(reldesc),
+ _(" which is an index on \"%s.%s\""),
+ hrel->nspname, hrel->relname);
+ /* Shift attention to index's table for toast check */
+ rel = hrel;
+ break;
+ }
+ }
+ if (i >= db->rel_arr.nrels)
+ snprintf(reldesc + strlen(reldesc),
+ sizeof(reldesc) - strlen(reldesc),
+ _(" which is an index on OID %u"), rel->indtable);
+ }
+ if (rel->toastheap)
+ {
+ for (i = 0; i < db->rel_arr.nrels; i++)
+ {
+ const RelInfo *brel = &db->rel_arr.rels[i];
+
+ if (brel->reloid == rel->toastheap)
+ {
+ snprintf(reldesc + strlen(reldesc),
+ sizeof(reldesc) - strlen(reldesc),
+ _(" which is the TOAST table for \"%s.%s\""),
+ brel->nspname, brel->relname);
+ break;
+ }
+ }
+ if (i >= db->rel_arr.nrels)
+ snprintf(reldesc + strlen(reldesc),
+ sizeof(reldesc) - strlen(reldesc),
+ _(" which is the TOAST table for OID %u"), rel->toastheap);
+ }
+
+ if (is_new_db)
+ pg_log(PG_WARNING, "No match found in old cluster for new relation with OID %u in database \"%s\": %s\n",
+ reloid, db->db_name, reldesc);
+ else
+ pg_log(PG_WARNING, "No match found in new cluster for old relation with OID %u in database \"%s\": %s\n",
+ reloid, db->db_name, reldesc);
+}
+
+/*
+ * get_db_and_rel_infos()
+ *
+ * higher level routine to generate dbinfos for the database running
+ * on the given "port". Assumes that server is already running.
+ */
+void
+get_db_and_rel_infos(ClusterInfo *cluster)
+{
+ int dbnum;
+
+ if (cluster->dbarr.dbs != NULL)
+ free_db_and_rel_infos(&cluster->dbarr);
+
+ get_db_infos(cluster);
+
+ for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
+ get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+
+ if (cluster == &old_cluster)
+ pg_log(PG_VERBOSE, "\nsource databases:\n");
+ else
+ pg_log(PG_VERBOSE, "\ntarget databases:\n");
+
+ if (log_opts.verbose)
+ print_db_infos(&cluster->dbarr);
+}
+
+
+/*
+ * get_db_infos()
+ *
+ * Scans pg_database system catalog and populates all user
+ * databases.
+ */
+static void
+get_db_infos(ClusterInfo *cluster)
+{
+ PGconn *conn = connectToServer(cluster, "template1");
+ PGresult *res;
+ int ntups;
+ int tupnum;
+ DbInfo *dbinfos;
+ int i_datname,
+ i_oid,
+ i_encoding,
+ i_datcollate,
+ i_datctype,
+ i_datlocprovider,
+ i_daticulocale,
+ i_spclocation;
+ char query[QUERY_ALLOC];
+
+ snprintf(query, sizeof(query),
+ "SELECT d.oid, d.datname, d.encoding, d.datcollate, d.datctype, ");
+ if (GET_MAJOR_VERSION(cluster->major_version) < 1500)
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "'c' AS datlocprovider, NULL AS daticulocale, ");
+ else
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "datlocprovider, daticulocale, ");
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "pg_catalog.pg_tablespace_location(t.oid) AS spclocation "
+ "FROM pg_catalog.pg_database d "
+ " LEFT OUTER JOIN pg_catalog.pg_tablespace t "
+ " ON d.dattablespace = t.oid "
+ "WHERE d.datallowconn = true "
+ "ORDER BY 1");
+
+ res = executeQueryOrDie(conn, "%s", query);
+
+ i_oid = PQfnumber(res, "oid");
+ i_datname = PQfnumber(res, "datname");
+ i_encoding = PQfnumber(res, "encoding");
+ i_datcollate = PQfnumber(res, "datcollate");
+ i_datctype = PQfnumber(res, "datctype");
+ i_datlocprovider = PQfnumber(res, "datlocprovider");
+ i_daticulocale = PQfnumber(res, "daticulocale");
+ i_spclocation = PQfnumber(res, "spclocation");
+
+ ntups = PQntuples(res);
+ dbinfos = (DbInfo *) pg_malloc(sizeof(DbInfo) * ntups);
+
+ for (tupnum = 0; tupnum < ntups; tupnum++)
+ {
+ dbinfos[tupnum].db_oid = atooid(PQgetvalue(res, tupnum, i_oid));
+ dbinfos[tupnum].db_name = pg_strdup(PQgetvalue(res, tupnum, i_datname));
+ dbinfos[tupnum].db_encoding = atoi(PQgetvalue(res, tupnum, i_encoding));
+ dbinfos[tupnum].db_collate = pg_strdup(PQgetvalue(res, tupnum, i_datcollate));
+ dbinfos[tupnum].db_ctype = pg_strdup(PQgetvalue(res, tupnum, i_datctype));
+ dbinfos[tupnum].db_collprovider = PQgetvalue(res, tupnum, i_datlocprovider)[0];
+ if (PQgetisnull(res, tupnum, i_daticulocale))
+ dbinfos[tupnum].db_iculocale = NULL;
+ else
+ dbinfos[tupnum].db_iculocale = pg_strdup(PQgetvalue(res, tupnum, i_daticulocale));
+ snprintf(dbinfos[tupnum].db_tablespace, sizeof(dbinfos[tupnum].db_tablespace), "%s",
+ PQgetvalue(res, tupnum, i_spclocation));
+ }
+ PQclear(res);
+
+ PQfinish(conn);
+
+ cluster->dbarr.dbs = dbinfos;
+ cluster->dbarr.ndbs = ntups;
+}
+
+
+/*
+ * get_rel_infos()
+ *
+ * gets the relinfos for all the user tables and indexes of the database
+ * referred to by "dbinfo".
+ *
+ * Note: the resulting RelInfo array is assumed to be sorted by OID.
+ * This allows later processing to match up old and new databases efficiently.
+ */
+static void
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+{
+ PGconn *conn = connectToServer(cluster,
+ dbinfo->db_name);
+ PGresult *res;
+ RelInfo *relinfos;
+ int ntups;
+ int relnum;
+ int num_rels = 0;
+ char *nspname = NULL;
+ char *relname = NULL;
+ char *tablespace = NULL;
+ int i_spclocation,
+ i_nspname,
+ i_relname,
+ i_reloid,
+ i_indtable,
+ i_toastheap,
+ i_relfilenode,
+ i_reltablespace;
+ char query[QUERY_ALLOC];
+ char *last_namespace = NULL,
+ *last_tablespace = NULL;
+
+ query[0] = '\0'; /* initialize query string to empty */
+
+ /*
+ * Create a CTE that collects OIDs of regular user tables and matviews,
+ * but excluding toast tables and indexes. We assume that relations with
+ * OIDs >= FirstNormalObjectId belong to the user. (That's probably
+ * redundant with the namespace-name exclusions, but let's be safe.)
+ *
+ * pg_largeobject contains user data that does not appear in pg_dump
+ * output, so we have to copy that system table. It's easiest to do that
+ * by treating it as a user table.
+ */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+
+ /*
+ * Add a CTE that collects OIDs of toast tables belonging to the tables
+ * selected by the regular_heap CTE. (We have to do this separately
+ * because the namespace-name rules above don't work for toast tables.)
+ */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " toast_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.reltoastrelid, 0::oid, c.oid "
+ " FROM regular_heap JOIN pg_catalog.pg_class c "
+ " ON regular_heap.reloid = c.oid "
+ " WHERE c.reltoastrelid != 0), ");
+
+ /*
+ * Add a CTE that collects OIDs of all valid indexes on the previously
+ * selected tables. We can ignore invalid indexes since pg_dump does.
+ * Testing indisready is necessary in 9.2, and harmless in earlier/later
+ * versions.
+ */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " all_index (reloid, indtable, toastheap) AS ( "
+ " SELECT indexrelid, indrelid, 0::oid "
+ " FROM pg_catalog.pg_index "
+ " WHERE indisvalid AND indisready "
+ " AND indrelid IN "
+ " (SELECT reloid FROM regular_heap "
+ " UNION ALL "
+ " SELECT reloid FROM toast_heap)) ");
+
+ /*
+ * And now we can write the query that retrieves the data we want for each
+ * heap and index relation. Make sure result is sorted by OID.
+ */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "SELECT all_rels.*, n.nspname, c.relname, "
+ " c.relfilenode, c.reltablespace, "
+ " pg_catalog.pg_tablespace_location(t.oid) AS spclocation "
+ "FROM (SELECT * FROM regular_heap "
+ " UNION ALL "
+ " SELECT * FROM toast_heap "
+ " UNION ALL "
+ " SELECT * FROM all_index) all_rels "
+ " JOIN pg_catalog.pg_class c "
+ " ON all_rels.reloid = c.oid "
+ " JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " LEFT OUTER JOIN pg_catalog.pg_tablespace t "
+ " ON c.reltablespace = t.oid "
+ "ORDER BY 1;");
+
+ res = executeQueryOrDie(conn, "%s", query);
+
+ ntups = PQntuples(res);
+
+ relinfos = (RelInfo *) pg_malloc(sizeof(RelInfo) * ntups);
+
+ i_reloid = PQfnumber(res, "reloid");
+ i_indtable = PQfnumber(res, "indtable");
+ i_toastheap = PQfnumber(res, "toastheap");
+ i_nspname = PQfnumber(res, "nspname");
+ i_relname = PQfnumber(res, "relname");
+ i_relfilenode = PQfnumber(res, "relfilenode");
+ i_reltablespace = PQfnumber(res, "reltablespace");
+ i_spclocation = PQfnumber(res, "spclocation");
+
+ for (relnum = 0; relnum < ntups; relnum++)
+ {
+ RelInfo *curr = &relinfos[num_rels++];
+
+ curr->reloid = atooid(PQgetvalue(res, relnum, i_reloid));
+ curr->indtable = atooid(PQgetvalue(res, relnum, i_indtable));
+ curr->toastheap = atooid(PQgetvalue(res, relnum, i_toastheap));
+
+ nspname = PQgetvalue(res, relnum, i_nspname);
+ curr->nsp_alloc = false;
+
+ /*
+ * Many of the namespace and tablespace strings are identical, so we
+ * try to reuse the allocated string pointers where possible to reduce
+ * memory consumption.
+ */
+ /* Can we reuse the previous string allocation? */
+ if (last_namespace && strcmp(nspname, last_namespace) == 0)
+ curr->nspname = last_namespace;
+ else
+ {
+ last_namespace = curr->nspname = pg_strdup(nspname);
+ curr->nsp_alloc = true;
+ }
+
+ relname = PQgetvalue(res, relnum, i_relname);
+ curr->relname = pg_strdup(relname);
+
+ curr->relfilenode = atooid(PQgetvalue(res, relnum, i_relfilenode));
+ curr->tblsp_alloc = false;
+
+ /* Is the tablespace oid non-default? */
+ if (atooid(PQgetvalue(res, relnum, i_reltablespace)) != 0)
+ {
+ /*
+ * The tablespace location might be "", meaning the cluster
+ * default location, i.e. pg_default or pg_global.
+ */
+ tablespace = PQgetvalue(res, relnum, i_spclocation);
+
+ /* Can we reuse the previous string allocation? */
+ if (last_tablespace && strcmp(tablespace, last_tablespace) == 0)
+ curr->tablespace = last_tablespace;
+ else
+ {
+ last_tablespace = curr->tablespace = pg_strdup(tablespace);
+ curr->tblsp_alloc = true;
+ }
+ }
+ else
+ /* A zero reltablespace oid indicates the database tablespace. */
+ curr->tablespace = dbinfo->db_tablespace;
+ }
+ PQclear(res);
+
+ PQfinish(conn);
+
+ dbinfo->rel_arr.rels = relinfos;
+ dbinfo->rel_arr.nrels = num_rels;
+}
+
+
+static void
+free_db_and_rel_infos(DbInfoArr *db_arr)
+{
+ int dbnum;
+
+ for (dbnum = 0; dbnum < db_arr->ndbs; dbnum++)
+ {
+ free_rel_infos(&db_arr->dbs[dbnum].rel_arr);
+ pg_free(db_arr->dbs[dbnum].db_name);
+ }
+ pg_free(db_arr->dbs);
+ db_arr->dbs = NULL;
+ db_arr->ndbs = 0;
+}
+
+
+static void
+free_rel_infos(RelInfoArr *rel_arr)
+{
+ int relnum;
+
+ for (relnum = 0; relnum < rel_arr->nrels; relnum++)
+ {
+ if (rel_arr->rels[relnum].nsp_alloc)
+ pg_free(rel_arr->rels[relnum].nspname);
+ pg_free(rel_arr->rels[relnum].relname);
+ if (rel_arr->rels[relnum].tblsp_alloc)
+ pg_free(rel_arr->rels[relnum].tablespace);
+ }
+ pg_free(rel_arr->rels);
+ rel_arr->nrels = 0;
+}
+
+
+static void
+print_db_infos(DbInfoArr *db_arr)
+{
+ int dbnum;
+
+ for (dbnum = 0; dbnum < db_arr->ndbs; dbnum++)
+ {
+ pg_log(PG_VERBOSE, "Database: %s\n", db_arr->dbs[dbnum].db_name);
+ print_rel_infos(&db_arr->dbs[dbnum].rel_arr);
+ pg_log(PG_VERBOSE, "\n\n");
+ }
+}
+
+
+static void
+print_rel_infos(RelInfoArr *rel_arr)
+{
+ int relnum;
+
+ for (relnum = 0; relnum < rel_arr->nrels; relnum++)
+ pg_log(PG_VERBOSE, "relname: %s.%s: reloid: %u reltblspace: %s\n",
+ rel_arr->rels[relnum].nspname,
+ rel_arr->rels[relnum].relname,
+ rel_arr->rels[relnum].reloid,
+ rel_arr->rels[relnum].tablespace);
+}