diff options
Diffstat (limited to 'src/bin/pg_upgrade/check.c')
-rw-r--r-- | src/bin/pg_upgrade/check.c | 1571 |
1 files changed, 1571 insertions, 0 deletions
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c new file mode 100644 index 0000000..2f3a673 --- /dev/null +++ b/src/bin/pg_upgrade/check.c @@ -0,0 +1,1571 @@ +/* + * check.c + * + * server checks and output routines + * + * Copyright (c) 2010-2022, PostgreSQL Global Development Group + * src/bin/pg_upgrade/check.c + */ + +#include "postgres_fe.h" + +#include "catalog/pg_authid_d.h" +#include "catalog/pg_collation.h" +#include "fe_utils/string_utils.h" +#include "mb/pg_wchar.h" +#include "pg_upgrade.h" + +static void check_new_cluster_is_empty(void); +static void check_databases_are_compatible(void); +static void check_locale_and_encoding(DbInfo *olddb, DbInfo *newdb); +static bool equivalent_locale(int category, const char *loca, const char *locb); +static void check_is_install_user(ClusterInfo *cluster); +static void check_proper_datallowconn(ClusterInfo *cluster); +static void check_for_prepared_transactions(ClusterInfo *cluster); +static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster); +static void check_for_user_defined_postfix_ops(ClusterInfo *cluster); +static void check_for_incompatible_polymorphics(ClusterInfo *cluster); +static void check_for_tables_with_oids(ClusterInfo *cluster); +static void check_for_composite_data_type_usage(ClusterInfo *cluster); +static void check_for_reg_data_type_usage(ClusterInfo *cluster); +static void check_for_removed_data_type_usage(ClusterInfo *cluster, + const char *version, + const char *datatype); +static void check_for_jsonb_9_4_usage(ClusterInfo *cluster); +static void check_for_pg_role_prefix(ClusterInfo *cluster); +static void check_for_new_tablespace_dir(ClusterInfo *new_cluster); +static void check_for_user_defined_encoding_conversions(ClusterInfo *cluster); +static char *get_canonical_locale_name(int category, const char *locale); + + +/* + * fix_path_separator + * For non-Windows, just return the argument. + * For Windows convert any forward slash to a backslash + * such as is suitable for arguments to builtin commands + * like RMDIR and DEL. + */ +static char * +fix_path_separator(char *path) +{ +#ifdef WIN32 + + char *result; + char *c; + + result = pg_strdup(path); + + for (c = result; *c != '\0'; c++) + if (*c == '/') + *c = '\\'; + + return result; +#else + + return path; +#endif +} + +void +output_check_banner(bool live_check) +{ + if (user_opts.check && live_check) + { + pg_log(PG_REPORT, + "Performing Consistency Checks on Old Live Server\n" + "------------------------------------------------\n"); + } + else + { + pg_log(PG_REPORT, + "Performing Consistency Checks\n" + "-----------------------------\n"); + } +} + + +void +check_and_dump_old_cluster(bool live_check) +{ + /* -- OLD -- */ + + if (!live_check) + start_postmaster(&old_cluster, true); + + /* Extract a list of databases and tables from the old cluster */ + get_db_and_rel_infos(&old_cluster); + + init_tablespaces(); + + get_loadable_libraries(); + + + /* + * Check for various failure cases + */ + check_is_install_user(&old_cluster); + check_proper_datallowconn(&old_cluster); + check_for_prepared_transactions(&old_cluster); + check_for_composite_data_type_usage(&old_cluster); + check_for_reg_data_type_usage(&old_cluster); + check_for_isn_and_int8_passing_mismatch(&old_cluster); + + /* + * PG 12 removed types abstime, reltime, tinterval. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1100) + { + check_for_removed_data_type_usage(&old_cluster, "12", "abstime"); + check_for_removed_data_type_usage(&old_cluster, "12", "reltime"); + check_for_removed_data_type_usage(&old_cluster, "12", "tinterval"); + } + + /* + * PG 14 changed the function signature of encoding conversion functions. + * Conversions from older versions cannot be upgraded automatically + * because the user-defined functions used by the encoding conversions + * need to be changed to match the new signature. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1300) + check_for_user_defined_encoding_conversions(&old_cluster); + + /* + * Pre-PG 14 allowed user defined postfix operators, which are not + * supported anymore. Verify there are none, iff applicable. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1300) + check_for_user_defined_postfix_ops(&old_cluster); + + /* + * PG 14 changed polymorphic functions from anyarray to + * anycompatiblearray. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1300) + check_for_incompatible_polymorphics(&old_cluster); + + /* + * Pre-PG 12 allowed tables to be declared WITH OIDS, which is not + * supported anymore. Verify there are none, iff applicable. + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1100) + check_for_tables_with_oids(&old_cluster); + + /* + * PG 12 changed the 'sql_identifier' type storage to be based on name, + * not varchar, which breaks on-disk format for existing data. So we need + * to prevent upgrade when used in user objects (tables, indexes, ...). + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 1100) + old_11_check_for_sql_identifier_data_type_usage(&old_cluster); + + /* + * Pre-PG 10 allowed tables with 'unknown' type columns and non WAL logged + * hash indexes + */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 906) + { + old_9_6_check_for_unknown_data_type_usage(&old_cluster); + if (user_opts.check) + old_9_6_invalidate_hash_indexes(&old_cluster, true); + } + + /* 9.5 and below should not have roles starting with pg_ */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 905) + check_for_pg_role_prefix(&old_cluster); + + if (GET_MAJOR_VERSION(old_cluster.major_version) == 904 && + old_cluster.controldata.cat_ver < JSONB_FORMAT_CHANGE_CAT_VER) + check_for_jsonb_9_4_usage(&old_cluster); + + /* Pre-PG 9.4 had a different 'line' data type internal format */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 903) + old_9_3_check_for_line_data_type_usage(&old_cluster); + + /* + * While not a check option, we do this now because this is the only time + * the old server is running. + */ + if (!user_opts.check) + generate_old_dump(); + + if (!live_check) + stop_postmaster(false); +} + + +void +check_new_cluster(void) +{ + get_db_and_rel_infos(&new_cluster); + + check_new_cluster_is_empty(); + check_databases_are_compatible(); + + check_loadable_libraries(); + + switch (user_opts.transfer_mode) + { + case TRANSFER_MODE_CLONE: + check_file_clone(); + break; + case TRANSFER_MODE_COPY: + break; + case TRANSFER_MODE_LINK: + check_hard_link(); + break; + } + + check_is_install_user(&new_cluster); + + check_for_prepared_transactions(&new_cluster); + + check_for_new_tablespace_dir(&new_cluster); +} + + +void +report_clusters_compatible(void) +{ + if (user_opts.check) + { + pg_log(PG_REPORT, "\n*Clusters are compatible*\n"); + /* stops new cluster */ + stop_postmaster(false); + + cleanup_output_dirs(); + exit(0); + } + + pg_log(PG_REPORT, "\n" + "If pg_upgrade fails after this point, you must re-initdb the\n" + "new cluster before continuing.\n"); +} + + +void +issue_warnings_and_set_wal_level(void) +{ + /* + * We unconditionally start/stop the new server because pg_resetwal -o set + * wal_level to 'minimum'. If the user is upgrading standby servers using + * the rsync instructions, they will need pg_upgrade to write its final + * WAL record showing wal_level as 'replica'. + */ + start_postmaster(&new_cluster, true); + + /* Reindex hash indexes for old < 10.0 */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 906) + old_9_6_invalidate_hash_indexes(&new_cluster, false); + + report_extension_updates(&new_cluster); + + stop_postmaster(false); +} + + +void +output_completion_banner(char *deletion_script_file_name) +{ + PQExpBufferData user_specification; + + initPQExpBuffer(&user_specification); + if (os_info.user_specified) + { + appendPQExpBufferStr(&user_specification, "-U "); + appendShellString(&user_specification, os_info.user); + appendPQExpBufferChar(&user_specification, ' '); + } + + pg_log(PG_REPORT, + "Optimizer statistics are not transferred by pg_upgrade.\n" + "Once you start the new server, consider running:\n" + " %s/vacuumdb %s--all --analyze-in-stages\n\n", new_cluster.bindir, user_specification.data); + + if (deletion_script_file_name) + pg_log(PG_REPORT, + "Running this script will delete the old cluster's data files:\n" + " %s\n", + deletion_script_file_name); + else + pg_log(PG_REPORT, + "Could not create a script to delete the old cluster's data files\n" + "because user-defined tablespaces or the new cluster's data directory\n" + "exist in the old cluster directory. The old cluster's contents must\n" + "be deleted manually.\n"); + + termPQExpBuffer(&user_specification); +} + + +void +check_cluster_versions(void) +{ + prep_status("Checking cluster versions"); + + /* cluster versions should already have been obtained */ + Assert(old_cluster.major_version != 0); + Assert(new_cluster.major_version != 0); + + /* + * We allow upgrades from/to the same major version for alpha/beta + * upgrades + */ + + if (GET_MAJOR_VERSION(old_cluster.major_version) < 902) + pg_fatal("This utility can only upgrade from PostgreSQL version %s and later.\n", + "9.2"); + + /* Only current PG version is supported as a target */ + if (GET_MAJOR_VERSION(new_cluster.major_version) != GET_MAJOR_VERSION(PG_VERSION_NUM)) + pg_fatal("This utility can only upgrade to PostgreSQL version %s.\n", + PG_MAJORVERSION); + + /* + * We can't allow downgrading because we use the target pg_dump, and + * pg_dump cannot operate on newer database versions, only current and + * older versions. + */ + if (old_cluster.major_version > new_cluster.major_version) + pg_fatal("This utility cannot be used to downgrade to older major PostgreSQL versions.\n"); + + /* Ensure binaries match the designated data directories */ + if (GET_MAJOR_VERSION(old_cluster.major_version) != + GET_MAJOR_VERSION(old_cluster.bin_version)) + pg_fatal("Old cluster data and binary directories are from different major versions.\n"); + if (GET_MAJOR_VERSION(new_cluster.major_version) != + GET_MAJOR_VERSION(new_cluster.bin_version)) + pg_fatal("New cluster data and binary directories are from different major versions.\n"); + + check_ok(); +} + + +void +check_cluster_compatibility(bool live_check) +{ + /* get/check pg_control data of servers */ + get_control_data(&old_cluster, live_check); + get_control_data(&new_cluster, false); + check_control_data(&old_cluster.controldata, &new_cluster.controldata); + + if (live_check && old_cluster.port == new_cluster.port) + pg_fatal("When checking a live server, " + "the old and new port numbers must be different.\n"); +} + + +/* + * check_locale_and_encoding() + * + * Check that locale and encoding of a database in the old and new clusters + * are compatible. + */ +static void +check_locale_and_encoding(DbInfo *olddb, DbInfo *newdb) +{ + if (olddb->db_encoding != newdb->db_encoding) + pg_fatal("encodings for database \"%s\" do not match: old \"%s\", new \"%s\"\n", + olddb->db_name, + pg_encoding_to_char(olddb->db_encoding), + pg_encoding_to_char(newdb->db_encoding)); + if (!equivalent_locale(LC_COLLATE, olddb->db_collate, newdb->db_collate)) + pg_fatal("lc_collate values for database \"%s\" do not match: old \"%s\", new \"%s\"\n", + olddb->db_name, olddb->db_collate, newdb->db_collate); + if (!equivalent_locale(LC_CTYPE, olddb->db_ctype, newdb->db_ctype)) + pg_fatal("lc_ctype values for database \"%s\" do not match: old \"%s\", new \"%s\"\n", + olddb->db_name, olddb->db_ctype, newdb->db_ctype); + if (olddb->db_collprovider != newdb->db_collprovider) + pg_fatal("locale providers for database \"%s\" do not match: old \"%s\", new \"%s\"\n", + olddb->db_name, + collprovider_name(olddb->db_collprovider), + collprovider_name(newdb->db_collprovider)); + if ((olddb->db_iculocale == NULL && newdb->db_iculocale != NULL) || + (olddb->db_iculocale != NULL && newdb->db_iculocale == NULL) || + (olddb->db_iculocale != NULL && newdb->db_iculocale != NULL && strcmp(olddb->db_iculocale, newdb->db_iculocale) != 0)) + pg_fatal("ICU locale values for database \"%s\" do not match: old \"%s\", new \"%s\"\n", + olddb->db_name, + olddb->db_iculocale ? olddb->db_iculocale : "(null)", + newdb->db_iculocale ? newdb->db_iculocale : "(null)"); +} + +/* + * equivalent_locale() + * + * Best effort locale-name comparison. Return false if we are not 100% sure + * the locales are equivalent. + * + * Note: The encoding parts of the names are ignored. This function is + * currently used to compare locale names stored in pg_database, and + * pg_database contains a separate encoding field. That's compared directly + * in check_locale_and_encoding(). + */ +static bool +equivalent_locale(int category, const char *loca, const char *locb) +{ + const char *chara; + const char *charb; + char *canona; + char *canonb; + int lena; + int lenb; + + /* + * If the names are equal, the locales are equivalent. Checking this first + * avoids calling setlocale() in the common case that the names are equal. + * That's a good thing, if setlocale() is buggy, for example. + */ + if (pg_strcasecmp(loca, locb) == 0) + return true; + + /* + * Not identical. Canonicalize both names, remove the encoding parts, and + * try again. + */ + canona = get_canonical_locale_name(category, loca); + chara = strrchr(canona, '.'); + lena = chara ? (chara - canona) : strlen(canona); + + canonb = get_canonical_locale_name(category, locb); + charb = strrchr(canonb, '.'); + lenb = charb ? (charb - canonb) : strlen(canonb); + + if (lena == lenb && pg_strncasecmp(canona, canonb, lena) == 0) + { + pg_free(canona); + pg_free(canonb); + return true; + } + + pg_free(canona); + pg_free(canonb); + return false; +} + + +static void +check_new_cluster_is_empty(void) +{ + int dbnum; + + for (dbnum = 0; dbnum < new_cluster.dbarr.ndbs; dbnum++) + { + int relnum; + RelInfoArr *rel_arr = &new_cluster.dbarr.dbs[dbnum].rel_arr; + + for (relnum = 0; relnum < rel_arr->nrels; + relnum++) + { + /* pg_largeobject and its index should be skipped */ + if (strcmp(rel_arr->rels[relnum].nspname, "pg_catalog") != 0) + pg_fatal("New cluster database \"%s\" is not empty: found relation \"%s.%s\"\n", + new_cluster.dbarr.dbs[dbnum].db_name, + rel_arr->rels[relnum].nspname, + rel_arr->rels[relnum].relname); + } + } +} + +/* + * Check that every database that already exists in the new cluster is + * compatible with the corresponding database in the old one. + */ +static void +check_databases_are_compatible(void) +{ + int newdbnum; + int olddbnum; + DbInfo *newdbinfo; + DbInfo *olddbinfo; + + for (newdbnum = 0; newdbnum < new_cluster.dbarr.ndbs; newdbnum++) + { + newdbinfo = &new_cluster.dbarr.dbs[newdbnum]; + + /* Find the corresponding database in the old cluster */ + for (olddbnum = 0; olddbnum < old_cluster.dbarr.ndbs; olddbnum++) + { + olddbinfo = &old_cluster.dbarr.dbs[olddbnum]; + if (strcmp(newdbinfo->db_name, olddbinfo->db_name) == 0) + { + check_locale_and_encoding(olddbinfo, newdbinfo); + break; + } + } + } +} + +/* + * A previous run of pg_upgrade might have failed and the new cluster + * directory recreated, but they might have forgotten to remove + * the new cluster's tablespace directories. Therefore, check that + * new cluster tablespace directories do not already exist. If + * they do, it would cause an error while restoring global objects. + * This allows the failure to be detected at check time, rather than + * during schema restore. + */ +static void +check_for_new_tablespace_dir(ClusterInfo *new_cluster) +{ + int tblnum; + char new_tablespace_dir[MAXPGPATH]; + + prep_status("Checking for new cluster tablespace directories"); + + for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) + { + struct stat statbuf; + + snprintf(new_tablespace_dir, MAXPGPATH, "%s%s", + os_info.old_tablespaces[tblnum], + new_cluster->tablespace_suffix); + + if (stat(new_tablespace_dir, &statbuf) == 0 || errno != ENOENT) + pg_fatal("new cluster tablespace directory already exists: \"%s\"\n", + new_tablespace_dir); + } + + check_ok(); +} + +/* + * create_script_for_old_cluster_deletion() + * + * This is particularly useful for tablespace deletion. + */ +void +create_script_for_old_cluster_deletion(char **deletion_script_file_name) +{ + FILE *script = NULL; + int tblnum; + char old_cluster_pgdata[MAXPGPATH], + new_cluster_pgdata[MAXPGPATH]; + + *deletion_script_file_name = psprintf("%sdelete_old_cluster.%s", + SCRIPT_PREFIX, SCRIPT_EXT); + + strlcpy(old_cluster_pgdata, old_cluster.pgdata, MAXPGPATH); + canonicalize_path(old_cluster_pgdata); + + strlcpy(new_cluster_pgdata, new_cluster.pgdata, MAXPGPATH); + canonicalize_path(new_cluster_pgdata); + + /* Some people put the new data directory inside the old one. */ + if (path_is_prefix_of_path(old_cluster_pgdata, new_cluster_pgdata)) + { + pg_log(PG_WARNING, + "\nWARNING: new data directory should not be inside the old data directory, i.e. %s\n", old_cluster_pgdata); + + /* Unlink file in case it is left over from a previous run. */ + unlink(*deletion_script_file_name); + pg_free(*deletion_script_file_name); + *deletion_script_file_name = NULL; + return; + } + + /* + * Some users (oddly) create tablespaces inside the cluster data + * directory. We can't create a proper old cluster delete script in that + * case. + */ + for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) + { + char old_tablespace_dir[MAXPGPATH]; + + strlcpy(old_tablespace_dir, os_info.old_tablespaces[tblnum], MAXPGPATH); + canonicalize_path(old_tablespace_dir); + if (path_is_prefix_of_path(old_cluster_pgdata, old_tablespace_dir)) + { + /* reproduce warning from CREATE TABLESPACE that is in the log */ + pg_log(PG_WARNING, + "\nWARNING: user-defined tablespace locations should not be inside the data directory, i.e. %s\n", old_tablespace_dir); + + /* Unlink file in case it is left over from a previous run. */ + unlink(*deletion_script_file_name); + pg_free(*deletion_script_file_name); + *deletion_script_file_name = NULL; + return; + } + } + + prep_status("Creating script to delete old cluster"); + + if ((script = fopen_priv(*deletion_script_file_name, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + *deletion_script_file_name, strerror(errno)); + +#ifndef WIN32 + /* add shebang header */ + fprintf(script, "#!/bin/sh\n\n"); +#endif + + /* delete old cluster's default tablespace */ + fprintf(script, RMDIR_CMD " %c%s%c\n", PATH_QUOTE, + fix_path_separator(old_cluster.pgdata), PATH_QUOTE); + + /* delete old cluster's alternate tablespaces */ + for (tblnum = 0; tblnum < os_info.num_old_tablespaces; tblnum++) + { + /* + * Do the old cluster's per-database directories share a directory + * with a new version-specific tablespace? + */ + if (strlen(old_cluster.tablespace_suffix) == 0) + { + /* delete per-database directories */ + int dbnum; + + fprintf(script, "\n"); + + for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++) + fprintf(script, RMDIR_CMD " %c%s%c%u%c\n", PATH_QUOTE, + fix_path_separator(os_info.old_tablespaces[tblnum]), + PATH_SEPARATOR, old_cluster.dbarr.dbs[dbnum].db_oid, + PATH_QUOTE); + } + else + { + char *suffix_path = pg_strdup(old_cluster.tablespace_suffix); + + /* + * Simply delete the tablespace directory, which might be ".old" + * or a version-specific subdirectory. + */ + fprintf(script, RMDIR_CMD " %c%s%s%c\n", PATH_QUOTE, + fix_path_separator(os_info.old_tablespaces[tblnum]), + fix_path_separator(suffix_path), PATH_QUOTE); + pfree(suffix_path); + } + } + + fclose(script); + +#ifndef WIN32 + if (chmod(*deletion_script_file_name, S_IRWXU) != 0) + pg_fatal("could not add execute permission to file \"%s\": %s\n", + *deletion_script_file_name, strerror(errno)); +#endif + + check_ok(); +} + + +/* + * check_is_install_user() + * + * Check we are the install user, and that the new cluster + * has no other users. + */ +static void +check_is_install_user(ClusterInfo *cluster) +{ + PGresult *res; + PGconn *conn = connectToServer(cluster, "template1"); + + prep_status("Checking database user is the install user"); + + /* Can't use pg_authid because only superusers can view it. */ + res = executeQueryOrDie(conn, + "SELECT rolsuper, oid " + "FROM pg_catalog.pg_roles " + "WHERE rolname = current_user " + "AND rolname !~ '^pg_'"); + + /* + * We only allow the install user in the new cluster (see comment below) + * and we preserve pg_authid.oid, so this must be the install user in the + * old cluster too. + */ + if (PQntuples(res) != 1 || + atooid(PQgetvalue(res, 0, 1)) != BOOTSTRAP_SUPERUSERID) + pg_fatal("database user \"%s\" is not the install user\n", + os_info.user); + + PQclear(res); + + res = executeQueryOrDie(conn, + "SELECT COUNT(*) " + "FROM pg_catalog.pg_roles " + "WHERE rolname !~ '^pg_'"); + + if (PQntuples(res) != 1) + pg_fatal("could not determine the number of users\n"); + + /* + * We only allow the install user in the new cluster because other defined + * users might match users defined in the old cluster and generate an + * error during pg_dump restore. + */ + if (cluster == &new_cluster && atooid(PQgetvalue(res, 0, 0)) != 1) + pg_fatal("Only the install user can be defined in the new cluster.\n"); + + PQclear(res); + + PQfinish(conn); + + check_ok(); +} + + +/* + * check_proper_datallowconn + * + * Ensure that all non-template0 databases allow connections since they + * otherwise won't be restored; and that template0 explicitly doesn't allow + * connections since it would make pg_dumpall --globals restore fail. + */ +static void +check_proper_datallowconn(ClusterInfo *cluster) +{ + int dbnum; + PGconn *conn_template1; + PGresult *dbres; + int ntups; + int i_datname; + int i_datallowconn; + FILE *script = NULL; + char output_path[MAXPGPATH]; + bool found = false; + + prep_status("Checking database connection settings"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "databases_with_datallowconn_false.txt"); + + conn_template1 = connectToServer(cluster, "template1"); + + /* get database names */ + dbres = executeQueryOrDie(conn_template1, + "SELECT datname, datallowconn " + "FROM pg_catalog.pg_database"); + + i_datname = PQfnumber(dbres, "datname"); + i_datallowconn = PQfnumber(dbres, "datallowconn"); + + ntups = PQntuples(dbres); + for (dbnum = 0; dbnum < ntups; dbnum++) + { + char *datname = PQgetvalue(dbres, dbnum, i_datname); + char *datallowconn = PQgetvalue(dbres, dbnum, i_datallowconn); + + if (strcmp(datname, "template0") == 0) + { + /* avoid restore failure when pg_dumpall tries to create template0 */ + if (strcmp(datallowconn, "t") == 0) + pg_fatal("template0 must not allow connections, " + "i.e. its pg_database.datallowconn must be false\n"); + } + else + { + /* + * avoid datallowconn == false databases from being skipped on + * restore + */ + if (strcmp(datallowconn, "f") == 0) + { + found = true; + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + + fprintf(script, "%s\n", datname); + } + } + } + + PQclear(dbres); + + PQfinish(conn_template1); + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("All non-template0 databases must allow connections, i.e. their\n" + "pg_database.datallowconn must be true. Your installation contains\n" + "non-template0 databases with their pg_database.datallowconn set to\n" + "false. Consider allowing connection for all non-template0 databases\n" + "or drop the databases which do not allow connections. A list of\n" + "databases with the problem is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + + +/* + * check_for_prepared_transactions() + * + * Make sure there are no prepared transactions because the storage format + * might have changed. + */ +static void +check_for_prepared_transactions(ClusterInfo *cluster) +{ + PGresult *res; + PGconn *conn = connectToServer(cluster, "template1"); + + prep_status("Checking for prepared transactions"); + + res = executeQueryOrDie(conn, + "SELECT * " + "FROM pg_catalog.pg_prepared_xacts"); + + if (PQntuples(res) != 0) + { + if (cluster == &old_cluster) + pg_fatal("The source cluster contains prepared transactions\n"); + else + pg_fatal("The target cluster contains prepared transactions\n"); + } + + PQclear(res); + + PQfinish(conn); + + check_ok(); +} + + +/* + * check_for_isn_and_int8_passing_mismatch() + * + * contrib/isn relies on data type int8, and in 8.4 int8 can now be passed + * by value. The schema dumps the CREATE TYPE PASSEDBYVALUE setting so + * it must match for the old and new servers. + */ +static void +check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for contrib/isn with bigint-passing mismatch"); + + if (old_cluster.controldata.float8_pass_by_value == + new_cluster.controldata.float8_pass_by_value) + { + /* no mismatch */ + check_ok(); + return; + } + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "contrib_isn_and_int8_pass_by_value.txt"); + + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_nspname, + i_proname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* Find any functions coming from contrib/isn */ + res = executeQueryOrDie(conn, + "SELECT n.nspname, p.proname " + "FROM pg_catalog.pg_proc p, " + " pg_catalog.pg_namespace n " + "WHERE p.pronamespace = n.oid AND " + " p.probin = '$libdir/isn'"); + + ntups = PQntuples(res); + i_nspname = PQfnumber(res, "nspname"); + i_proname = PQfnumber(res, "proname"); + for (rowno = 0; rowno < ntups; rowno++) + { + found = true; + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " %s.%s\n", + PQgetvalue(res, rowno, i_nspname), + PQgetvalue(res, rowno, i_proname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains \"contrib/isn\" functions which rely on the\n" + "bigint data type. Your old and new clusters pass bigint values\n" + "differently so this cluster cannot currently be upgraded. You can\n" + "manually dump databases in the old cluster that use \"contrib/isn\"\n" + "facilities, drop them, perform the upgrade, and then restore them. A\n" + "list of the problem functions is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + +/* + * Verify that no user defined postfix operators exist. + */ +static void +check_for_user_defined_postfix_ops(ClusterInfo *cluster) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for user-defined postfix operators"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "postfix_ops.txt"); + + /* Find any user defined postfix operators */ + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_oproid, + i_oprnsp, + i_oprname, + i_typnsp, + i_typname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* + * The query below hardcodes FirstNormalObjectId as 16384 rather than + * interpolating that C #define into the query because, if that + * #define is ever changed, the cutoff we want to use is the value + * used by pre-version 14 servers, not that of some future version. + */ + res = executeQueryOrDie(conn, + "SELECT o.oid AS oproid, " + " n.nspname AS oprnsp, " + " o.oprname, " + " tn.nspname AS typnsp, " + " t.typname " + "FROM pg_catalog.pg_operator o, " + " pg_catalog.pg_namespace n, " + " pg_catalog.pg_type t, " + " pg_catalog.pg_namespace tn " + "WHERE o.oprnamespace = n.oid AND " + " o.oprleft = t.oid AND " + " t.typnamespace = tn.oid AND " + " o.oprright = 0 AND " + " o.oid >= 16384"); + ntups = PQntuples(res); + i_oproid = PQfnumber(res, "oproid"); + i_oprnsp = PQfnumber(res, "oprnsp"); + i_oprname = PQfnumber(res, "oprname"); + i_typnsp = PQfnumber(res, "typnsp"); + i_typname = PQfnumber(res, "typname"); + for (rowno = 0; rowno < ntups; rowno++) + { + found = true; + if (script == NULL && + (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " (oid=%s) %s.%s (%s.%s, NONE)\n", + PQgetvalue(res, rowno, i_oproid), + PQgetvalue(res, rowno, i_oprnsp), + PQgetvalue(res, rowno, i_oprname), + PQgetvalue(res, rowno, i_typnsp), + PQgetvalue(res, rowno, i_typname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains user-defined postfix operators, which are not\n" + "supported anymore. Consider dropping the postfix operators and replacing\n" + "them with prefix operators or function calls.\n" + "A list of user-defined postfix operators is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + +/* + * check_for_incompatible_polymorphics() + * + * Make sure nothing is using old polymorphic functions with + * anyarray/anyelement rather than the new anycompatible variants. + */ +static void +check_for_incompatible_polymorphics(ClusterInfo *cluster) +{ + PGresult *res; + FILE *script = NULL; + char output_path[MAXPGPATH]; + PQExpBufferData old_polymorphics; + + prep_status("Checking for incompatible polymorphic functions"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "incompatible_polymorphics.txt"); + + /* The set of problematic functions varies a bit in different versions */ + initPQExpBuffer(&old_polymorphics); + + appendPQExpBufferStr(&old_polymorphics, + "'array_append(anyarray,anyelement)'" + ", 'array_cat(anyarray,anyarray)'" + ", 'array_prepend(anyelement,anyarray)'"); + + if (GET_MAJOR_VERSION(cluster->major_version) >= 903) + appendPQExpBufferStr(&old_polymorphics, + ", 'array_remove(anyarray,anyelement)'" + ", 'array_replace(anyarray,anyelement,anyelement)'"); + + if (GET_MAJOR_VERSION(cluster->major_version) >= 905) + appendPQExpBufferStr(&old_polymorphics, + ", 'array_position(anyarray,anyelement)'" + ", 'array_position(anyarray,anyelement,integer)'" + ", 'array_positions(anyarray,anyelement)'" + ", 'width_bucket(anyelement,anyarray)'"); + + for (int dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + bool db_used = false; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + int ntups; + int i_objkind, + i_objname; + + /* + * The query below hardcodes FirstNormalObjectId as 16384 rather than + * interpolating that C #define into the query because, if that + * #define is ever changed, the cutoff we want to use is the value + * used by pre-version 14 servers, not that of some future version. + */ + res = executeQueryOrDie(conn, + /* Aggregate transition functions */ + "SELECT 'aggregate' AS objkind, p.oid::regprocedure::text AS objname " + "FROM pg_proc AS p " + "JOIN pg_aggregate AS a ON a.aggfnoid=p.oid " + "JOIN pg_proc AS transfn ON transfn.oid=a.aggtransfn " + "WHERE p.oid >= 16384 " + "AND a.aggtransfn = ANY(ARRAY[%s]::regprocedure[]) " + "AND a.aggtranstype = ANY(ARRAY['anyarray', 'anyelement']::regtype[]) " + + /* Aggregate final functions */ + "UNION ALL " + "SELECT 'aggregate' AS objkind, p.oid::regprocedure::text AS objname " + "FROM pg_proc AS p " + "JOIN pg_aggregate AS a ON a.aggfnoid=p.oid " + "JOIN pg_proc AS finalfn ON finalfn.oid=a.aggfinalfn " + "WHERE p.oid >= 16384 " + "AND a.aggfinalfn = ANY(ARRAY[%s]::regprocedure[]) " + "AND a.aggtranstype = ANY(ARRAY['anyarray', 'anyelement']::regtype[]) " + + /* Operators */ + "UNION ALL " + "SELECT 'operator' AS objkind, op.oid::regoperator::text AS objname " + "FROM pg_operator AS op " + "WHERE op.oid >= 16384 " + "AND oprcode = ANY(ARRAY[%s]::regprocedure[]) " + "AND oprleft = ANY(ARRAY['anyarray', 'anyelement']::regtype[]);", + old_polymorphics.data, + old_polymorphics.data, + old_polymorphics.data); + + ntups = PQntuples(res); + + i_objkind = PQfnumber(res, "objkind"); + i_objname = PQfnumber(res, "objname"); + + for (int rowno = 0; rowno < ntups; rowno++) + { + if (script == NULL && + (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + + fprintf(script, " %s: %s\n", + PQgetvalue(res, rowno, i_objkind), + PQgetvalue(res, rowno, i_objname)); + } + + PQclear(res); + PQfinish(conn); + } + + if (script) + { + fclose(script); + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains user-defined objects that refer to internal\n" + "polymorphic functions with arguments of type \"anyarray\" or \"anyelement\".\n" + "These user-defined objects must be dropped before upgrading and restored\n" + "afterwards, changing them to refer to the new corresponding functions with\n" + "arguments of type \"anycompatiblearray\" and \"anycompatible\".\n" + "A list of the problematic objects is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); + + termPQExpBuffer(&old_polymorphics); +} + +/* + * Verify that no tables are declared WITH OIDS. + */ +static void +check_for_tables_with_oids(ClusterInfo *cluster) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for tables WITH OIDS"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "tables_with_oids.txt"); + + /* Find any tables declared WITH OIDS */ + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_nspname, + i_relname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + res = executeQueryOrDie(conn, + "SELECT n.nspname, c.relname " + "FROM pg_catalog.pg_class c, " + " pg_catalog.pg_namespace n " + "WHERE c.relnamespace = n.oid AND " + " c.relhasoids AND" + " n.nspname NOT IN ('pg_catalog')"); + + ntups = PQntuples(res); + i_nspname = PQfnumber(res, "nspname"); + i_relname = PQfnumber(res, "relname"); + for (rowno = 0; rowno < ntups; rowno++) + { + found = true; + if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " %s.%s\n", + PQgetvalue(res, rowno, i_nspname), + PQgetvalue(res, rowno, i_relname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains tables declared WITH OIDS, which is not\n" + "supported anymore. Consider removing the oid column using\n" + " ALTER TABLE ... SET WITHOUT OIDS;\n" + "A list of tables with the problem is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + + +/* + * check_for_composite_data_type_usage() + * Check for system-defined composite types used in user tables. + * + * The OIDs of rowtypes of system catalogs and information_schema views + * can change across major versions; unlike user-defined types, we have + * no mechanism for forcing them to be the same in the new cluster. + * Hence, if any user table uses one, that's problematic for pg_upgrade. + */ +static void +check_for_composite_data_type_usage(ClusterInfo *cluster) +{ + bool found; + Oid firstUserOid; + char output_path[MAXPGPATH]; + char *base_query; + + prep_status("Checking for system-defined composite types in user tables"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "tables_using_composite.txt"); + + /* + * Look for composite types that were made during initdb *or* belong to + * information_schema; that's important in case information_schema was + * dropped and reloaded. + * + * The cutoff OID here should match the source cluster's value of + * FirstNormalObjectId. We hardcode it rather than using that C #define + * because, if that #define is ever changed, our own version's value is + * NOT what to use. Eventually we may need a test on the source cluster's + * version to select the correct value. + */ + firstUserOid = 16384; + + base_query = psprintf("SELECT t.oid FROM pg_catalog.pg_type t " + "LEFT JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid " + " WHERE typtype = 'c' AND (t.oid < %u OR nspname = 'information_schema')", + firstUserOid); + + found = check_for_data_types_usage(cluster, base_query, output_path); + + free(base_query); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains system-defined composite type(s) in user tables.\n" + "These type OIDs are not stable across PostgreSQL versions,\n" + "so this cluster cannot currently be upgraded. You can\n" + "drop the problem columns and restart the upgrade.\n" + "A list of the problem columns is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + +/* + * check_for_reg_data_type_usage() + * pg_upgrade only preserves these system values: + * pg_class.oid + * pg_type.oid + * pg_enum.oid + * + * Many of the reg* data types reference system catalog info that is + * not preserved, and hence these data types cannot be used in user + * tables upgraded by pg_upgrade. + */ +static void +check_for_reg_data_type_usage(ClusterInfo *cluster) +{ + bool found; + char output_path[MAXPGPATH]; + + prep_status("Checking for reg* data types in user tables"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "tables_using_reg.txt"); + + /* + * Note: older servers will not have all of these reg* types, so we have + * to write the query like this rather than depending on casts to regtype. + */ + found = check_for_data_types_usage(cluster, + "SELECT oid FROM pg_catalog.pg_type t " + "WHERE t.typnamespace = " + " (SELECT oid FROM pg_catalog.pg_namespace " + " WHERE nspname = 'pg_catalog') " + " AND t.typname IN ( " + /* pg_class.oid is preserved, so 'regclass' is OK */ + " 'regcollation', " + " 'regconfig', " + " 'regdictionary', " + " 'regnamespace', " + " 'regoper', " + " 'regoperator', " + " 'regproc', " + " 'regprocedure' " + /* pg_authid.oid is preserved, so 'regrole' is OK */ + /* pg_type.oid is (mostly) preserved, so 'regtype' is OK */ + " )", + output_path); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains one of the reg* data types in user tables.\n" + "These data types reference system OIDs that are not preserved by\n" + "pg_upgrade, so this cluster cannot currently be upgraded. You can\n" + "drop the problem columns and restart the upgrade.\n" + "A list of the problem columns is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + +/* + * check_for_removed_data_type_usage + * + * Check for in-core data types that have been removed. Callers know + * the exact list. + */ +static void +check_for_removed_data_type_usage(ClusterInfo *cluster, const char *version, + const char *datatype) +{ + char output_path[MAXPGPATH]; + char typename[NAMEDATALEN]; + + prep_status("Checking for removed \"%s\" data type in user tables", + datatype); + + snprintf(output_path, sizeof(output_path), "tables_using_%s.txt", + datatype); + snprintf(typename, sizeof(typename), "pg_catalog.%s", datatype); + + if (check_for_data_type_usage(cluster, typename, output_path)) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains the \"%s\" data type in user tables.\n" + "The \"%s\" type has been removed in PostgreSQL version %s,\n" + "so this cluster cannot currently be upgraded. You can drop the\n" + "problem columns, or change them to another data type, and restart\n" + "the upgrade. A list of the problem columns is in the file:\n" + " %s\n\n", datatype, datatype, version, output_path); + } + else + check_ok(); +} + + +/* + * check_for_jsonb_9_4_usage() + * + * JSONB changed its storage format during 9.4 beta, so check for it. + */ +static void +check_for_jsonb_9_4_usage(ClusterInfo *cluster) +{ + char output_path[MAXPGPATH]; + + prep_status("Checking for incompatible \"jsonb\" data type"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "tables_using_jsonb.txt"); + + if (check_for_data_type_usage(cluster, "pg_catalog.jsonb", output_path)) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains the \"jsonb\" data type in user tables.\n" + "The internal format of \"jsonb\" changed during 9.4 beta so this\n" + "cluster cannot currently be upgraded. You can\n" + "drop the problem columns and restart the upgrade.\n" + "A list of the problem columns is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + +/* + * check_for_pg_role_prefix() + * + * Versions older than 9.6 should not have any pg_* roles + */ +static void +check_for_pg_role_prefix(ClusterInfo *cluster) +{ + PGresult *res; + PGconn *conn = connectToServer(cluster, "template1"); + + prep_status("Checking for roles starting with \"pg_\""); + + res = executeQueryOrDie(conn, + "SELECT * " + "FROM pg_catalog.pg_roles " + "WHERE rolname ~ '^pg_'"); + + if (PQntuples(res) != 0) + { + if (cluster == &old_cluster) + pg_fatal("The source cluster contains roles starting with \"pg_\"\n"); + else + pg_fatal("The target cluster contains roles starting with \"pg_\"\n"); + } + + PQclear(res); + + PQfinish(conn); + + check_ok(); +} + +/* + * Verify that no user-defined encoding conversions exist. + */ +static void +check_for_user_defined_encoding_conversions(ClusterInfo *cluster) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for user-defined encoding conversions"); + + snprintf(output_path, sizeof(output_path), "%s/%s", + log_opts.basedir, + "encoding_conversions.txt"); + + /* Find any user defined encoding conversions */ + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_conoid, + i_conname, + i_nspname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* + * The query below hardcodes FirstNormalObjectId as 16384 rather than + * interpolating that C #define into the query because, if that + * #define is ever changed, the cutoff we want to use is the value + * used by pre-version 14 servers, not that of some future version. + */ + res = executeQueryOrDie(conn, + "SELECT c.oid as conoid, c.conname, n.nspname " + "FROM pg_catalog.pg_conversion c, " + " pg_catalog.pg_namespace n " + "WHERE c.connamespace = n.oid AND " + " c.oid >= 16384"); + ntups = PQntuples(res); + i_conoid = PQfnumber(res, "conoid"); + i_conname = PQfnumber(res, "conname"); + i_nspname = PQfnumber(res, "nspname"); + for (rowno = 0; rowno < ntups; rowno++) + { + found = true; + if (script == NULL && + (script = fopen_priv(output_path, "w")) == NULL) + pg_fatal("could not open file \"%s\": %s\n", + output_path, strerror(errno)); + if (!db_used) + { + fprintf(script, "In database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " (oid=%s) %s.%s\n", + PQgetvalue(res, rowno, i_conoid), + PQgetvalue(res, rowno, i_nspname), + PQgetvalue(res, rowno, i_conname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains user-defined encoding conversions.\n" + "The conversion function parameters changed in PostgreSQL version 14\n" + "so this cluster cannot currently be upgraded. You can remove the\n" + "encoding conversions in the old cluster and restart the upgrade.\n" + "A list of user-defined encoding conversions is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + + +/* + * get_canonical_locale_name + * + * Send the locale name to the system, and hope we get back a canonical + * version. This should match the backend's check_locale() function. + */ +static char * +get_canonical_locale_name(int category, const char *locale) +{ + char *save; + char *res; + + /* get the current setting, so we can restore it. */ + save = setlocale(category, NULL); + if (!save) + pg_fatal("failed to get the current locale\n"); + + /* 'save' may be pointing at a modifiable scratch variable, so copy it. */ + save = pg_strdup(save); + + /* set the locale with setlocale, to see if it accepts it. */ + res = setlocale(category, locale); + + if (!res) + pg_fatal("failed to get system locale name for \"%s\"\n", locale); + + res = pg_strdup(res); + + /* restore old value. */ + if (!setlocale(category, save)) + pg_fatal("failed to restore old locale \"%s\"\n", save); + + pg_free(save); + + return res; +} |