diff options
Diffstat (limited to 'src/bin/pg_upgrade/check.c')
-rw-r--r-- | src/bin/pg_upgrade/check.c | 1016 |
1 files changed, 1016 insertions, 0 deletions
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c new file mode 100644 index 00000000000..647bf349f4d --- /dev/null +++ b/src/bin/pg_upgrade/check.c @@ -0,0 +1,1016 @@ +/* + * check.c + * + * server checks and output routines + * + * Copyright (c) 2010-2015, PostgreSQL Global Development Group + * src/bin/pg_upgrade/check.c + */ + +#include "postgres_fe.h" + +#include "catalog/pg_authid.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_for_prepared_transactions(ClusterInfo *cluster); +static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster); +static void check_for_reg_data_type_usage(ClusterInfo *cluster); +static void check_for_jsonb_9_4_usage(ClusterInfo *cluster); +static void get_bin_version(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"); + pg_log(PG_REPORT, "------------------------------------------------\n"); + } + else + { + pg_log(PG_REPORT, "Performing Consistency Checks\n"); + pg_log(PG_REPORT, "-----------------------------\n"); + } +} + + +void +check_and_dump_old_cluster(bool live_check) +{ + /* -- OLD -- */ + + if (!live_check) + start_postmaster(&old_cluster, true); + + get_pg_database_relfilenode(&old_cluster); + + /* 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_for_prepared_transactions(&old_cluster); + check_for_reg_data_type_usage(&old_cluster); + check_for_isn_and_int8_passing_mismatch(&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); + + /* Pre-PG 9.0 had no large object permissions */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 804) + new_9_0_populate_pg_largeobject_metadata(&old_cluster, true); + + /* + * 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(); + + if (user_opts.transfer_mode == TRANSFER_MODE_LINK) + check_hard_link(); + + check_is_install_user(&new_cluster); + + check_for_prepared_transactions(&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); + 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(void) +{ + /* Create dummy large object permissions for old < PG 9.0? */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 804) + { + start_postmaster(&new_cluster, true); + new_9_0_populate_pg_largeobject_metadata(&new_cluster, false); + stop_postmaster(false); + } +} + + +void +output_completion_banner(char *analyze_script_file_name, + char *deletion_script_file_name) +{ + /* Did we copy the free space files? */ + if (GET_MAJOR_VERSION(old_cluster.major_version) >= 804) + pg_log(PG_REPORT, + "Optimizer statistics are not transferred by pg_upgrade so,\n" + "once you start the new server, consider running:\n" + " %s\n\n", analyze_script_file_name); + else + pg_log(PG_REPORT, + "Optimizer statistics and free space information are not transferred\n" + "by pg_upgrade so, once you start the new server, consider running:\n" + " %s\n\n", analyze_script_file_name); + + + 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\n" + "files because user-defined tablespaces exist in the old cluster\n" + "directory. The old cluster's contents must be deleted manually.\n"); +} + + +void +check_cluster_versions(void) +{ + prep_status("Checking cluster versions"); + + /* get old and new cluster versions */ + old_cluster.major_version = get_major_server_version(&old_cluster); + new_cluster.major_version = get_major_server_version(&new_cluster); + + /* + * We allow upgrades from/to the same major version for alpha/beta + * upgrades + */ + + if (GET_MAJOR_VERSION(old_cluster.major_version) < 804) + pg_fatal("This utility can only upgrade from PostgreSQL version 8.4 and later.\n"); + + /* 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"); + + /* get old and new binary versions */ + get_bin_version(&old_cluster); + get_bin_version(&new_cluster); + + /* 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); + + /* Is it 9.0 but without tablespace directories? */ + if (GET_MAJOR_VERSION(new_cluster.major_version) == 900 && + new_cluster.controldata.cat_ver < TABLE_SPACE_SUBDIRS_CAT_VER) + pg_fatal("This utility can only upgrade to PostgreSQL version 9.0 after 2010-01-11\n" + "because of backend API changes made during development.\n"); + + /* We read the real port number for PG >= 9.1 */ + if (live_check && GET_MAJOR_VERSION(old_cluster.major_version) < 901 && + old_cluster.port == DEF_PGUPORT) + pg_fatal("When checking a pre-PG 9.1 live old server, " + "you must specify the old server's port number.\n"); + + 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); +} + +/* + * 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) + return true; + + 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\n", + new_cluster.dbarr.dbs[dbnum].db_name); + } + } +} + +/* + * 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; + } + } + } +} + + +/* + * create_script_for_cluster_analyze() + * + * This incrementally generates better optimizer statistics + */ +void +create_script_for_cluster_analyze(char **analyze_script_file_name) +{ + FILE *script = NULL; + char *user_specification = ""; + + prep_status("Creating script to analyze new cluster"); + + if (os_info.user_specified) + user_specification = psprintf("-U \"%s\" ", os_info.user); + + *analyze_script_file_name = psprintf("%sanalyze_new_cluster.%s", + SCRIPT_PREFIX, SCRIPT_EXT); + + if ((script = fopen_priv(*analyze_script_file_name, "w")) == NULL) + pg_fatal("Could not open file \"%s\": %s\n", + *analyze_script_file_name, getErrorText(errno)); + +#ifndef WIN32 + /* add shebang header */ + fprintf(script, "#!/bin/sh\n\n"); +#else + /* suppress command echoing */ + fprintf(script, "@echo off\n"); +#endif + + fprintf(script, "echo %sThis script will generate minimal optimizer statistics rapidly%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %sso your system is usable, and then gather statistics twice more%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %swith increasing accuracy. When it is done, your system will%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %shave the default level of optimizer statistics.%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo%s\n\n", ECHO_BLANK); + + fprintf(script, "echo %sIf you have used ALTER TABLE to modify the statistics target for%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %sany tables, you might want to remove them and restore them after%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %srunning this script because they will delay fast statistics generation.%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo%s\n\n", ECHO_BLANK); + + fprintf(script, "echo %sIf you would like default statistics as quickly as possible, cancel%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %sthis script and run:%s\n", + ECHO_QUOTE, ECHO_QUOTE); + fprintf(script, "echo %s \"%s/vacuumdb\" %s--all %s%s\n", ECHO_QUOTE, + new_cluster.bindir, user_specification, + /* Did we copy the free space files? */ + (GET_MAJOR_VERSION(old_cluster.major_version) >= 804) ? + "--analyze-only" : "--analyze", ECHO_QUOTE); + fprintf(script, "echo%s\n\n", ECHO_BLANK); + + fprintf(script, "\"%s/vacuumdb\" %s--all --analyze-in-stages\n", + new_cluster.bindir, user_specification); + /* Did we copy the free space files? */ + if (GET_MAJOR_VERSION(old_cluster.major_version) < 804) + fprintf(script, "\"%s/vacuumdb\" %s--all\n", new_cluster.bindir, + user_specification); + + fprintf(script, "echo%s\n\n", ECHO_BLANK); + fprintf(script, "echo %sDone%s\n", + ECHO_QUOTE, ECHO_QUOTE); + + fclose(script); + +#ifndef WIN32 + if (chmod(*analyze_script_file_name, S_IRWXU) != 0) + pg_fatal("Could not add execute permission to file \"%s\": %s\n", + *analyze_script_file_name, getErrorText(errno)); +#endif + + if (os_info.user_specified) + pg_free(user_specification); + + 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]; + + *deletion_script_file_name = psprintf("%sdelete_old_cluster.%s", + SCRIPT_PREFIX, SCRIPT_EXT); + + /* + * Some users (oddly) create tablespaces inside the cluster data + * directory. We can't create a proper old cluster delete script in that + * case. + */ + strlcpy(old_cluster_pgdata, old_cluster.pgdata, MAXPGPATH); + canonicalize_path(old_cluster_pgdata); + 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)) + { + /* 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, getErrorText(errno)); + +#ifndef WIN32 + /* add shebang header */ + fprintf(script, "#!/bin/sh\n\n"); +#endif + + /* delete old cluster's default tablespace */ + fprintf(script, RMDIR_CMD " \"%s\"\n", fix_path_separator(old_cluster.pgdata)); + + /* 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"); + /* remove PG_VERSION? */ + if (GET_MAJOR_VERSION(old_cluster.major_version) <= 804) + fprintf(script, RM_CMD " %s%cPG_VERSION\n", + fix_path_separator(os_info.old_tablespaces[tblnum]), + PATH_SEPARATOR); + + for (dbnum = 0; dbnum < old_cluster.dbarr.ndbs; dbnum++) + fprintf(script, RMDIR_CMD " \"%s%c%d\"\n", + fix_path_separator(os_info.old_tablespaces[tblnum]), + PATH_SEPARATOR, old_cluster.dbarr.dbs[dbnum].db_oid); + } + 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 " \"%s%s\"\n", + fix_path_separator(os_info.old_tablespaces[tblnum]), + fix_path_separator(suffix_path)); + 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, getErrorText(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"); + + /* + * 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 "); + + 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_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) + pg_fatal("The %s cluster contains prepared transactions\n", + CLUSTER_NAME(cluster)); + + 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), + "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, getErrorText(errno)); + if (!db_used) + { + fprintf(script, "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 upgrade databases that use \"contrib/isn\" facilities and remove\n" + "\"contrib/isn\" from the old cluster and restart the upgrade. A list of\n" + "the problem functions 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) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for reg* system OID user data types"); + + snprintf(output_path, sizeof(output_path), "tables_using_reg.txt"); + + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_nspname, + i_relname, + i_attname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* + * While several relkinds don't store any data, e.g. views, they can + * be used to define data types of other columns, so we check all + * relkinds. + */ + res = executeQueryOrDie(conn, + "SELECT n.nspname, c.relname, a.attname " + "FROM pg_catalog.pg_class c, " + " pg_catalog.pg_namespace n, " + " pg_catalog.pg_attribute a " + "WHERE c.oid = a.attrelid AND " + " NOT a.attisdropped AND " + " a.atttypid IN ( " + " 'pg_catalog.regproc'::pg_catalog.regtype, " + " 'pg_catalog.regprocedure'::pg_catalog.regtype, " + " 'pg_catalog.regoper'::pg_catalog.regtype, " + " 'pg_catalog.regoperator'::pg_catalog.regtype, " + /* regclass.oid is preserved, so 'regclass' is OK */ + /* regtype.oid is preserved, so 'regtype' is OK */ + " 'pg_catalog.regconfig'::pg_catalog.regtype, " + " 'pg_catalog.regdictionary'::pg_catalog.regtype) AND " + " c.relnamespace = n.oid AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema')"); + + ntups = PQntuples(res); + i_nspname = PQfnumber(res, "nspname"); + i_relname = PQfnumber(res, "relname"); + i_attname = PQfnumber(res, "attname"); + 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, getErrorText(errno)); + if (!db_used) + { + fprintf(script, "Database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " %s.%s.%s\n", + PQgetvalue(res, rowno, i_nspname), + PQgetvalue(res, rowno, i_relname), + PQgetvalue(res, rowno, i_attname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + 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" + "remove the problem tables and restart the upgrade. A list of the problem\n" + "columns is in the file:\n" + " %s\n\n", 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) +{ + int dbnum; + FILE *script = NULL; + bool found = false; + char output_path[MAXPGPATH]; + + prep_status("Checking for JSONB user data types"); + + snprintf(output_path, sizeof(output_path), "tables_using_jsonb.txt"); + + for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) + { + PGresult *res; + bool db_used = false; + int ntups; + int rowno; + int i_nspname, + i_relname, + i_attname; + DbInfo *active_db = &cluster->dbarr.dbs[dbnum]; + PGconn *conn = connectToServer(cluster, active_db->db_name); + + /* + * While several relkinds don't store any data, e.g. views, they can + * be used to define data types of other columns, so we check all + * relkinds. + */ + res = executeQueryOrDie(conn, + "SELECT n.nspname, c.relname, a.attname " + "FROM pg_catalog.pg_class c, " + " pg_catalog.pg_namespace n, " + " pg_catalog.pg_attribute a " + "WHERE c.oid = a.attrelid AND " + " NOT a.attisdropped AND " + " a.atttypid = 'pg_catalog.jsonb'::pg_catalog.regtype AND " + " c.relnamespace = n.oid AND " + /* exclude possible orphaned temp tables */ + " n.nspname !~ '^pg_temp_' AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema')"); + + ntups = PQntuples(res); + i_nspname = PQfnumber(res, "nspname"); + i_relname = PQfnumber(res, "relname"); + i_attname = PQfnumber(res, "attname"); + 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, getErrorText(errno)); + if (!db_used) + { + fprintf(script, "Database: %s\n", active_db->db_name); + db_used = true; + } + fprintf(script, " %s.%s.%s\n", + PQgetvalue(res, rowno, i_nspname), + PQgetvalue(res, rowno, i_relname), + PQgetvalue(res, rowno, i_attname)); + } + + PQclear(res); + + PQfinish(conn); + } + + if (script) + fclose(script); + + if (found) + { + pg_log(PG_REPORT, "fatal\n"); + pg_fatal("Your installation contains one of the JSONB data types in user tables.\n" + "The internal format of JSONB changed during 9.4 beta so this cluster cannot currently\n" + "be upgraded. You can remove the problem tables and restart the upgrade. A list\n" + "of the problem columns is in the file:\n" + " %s\n\n", output_path); + } + else + check_ok(); +} + + +static void +get_bin_version(ClusterInfo *cluster) +{ + char cmd[MAXPGPATH], + cmd_output[MAX_STRING]; + FILE *output; + int pre_dot, + post_dot; + + snprintf(cmd, sizeof(cmd), "\"%s/pg_ctl\" --version", cluster->bindir); + + if ((output = popen(cmd, "r")) == NULL || + fgets(cmd_output, sizeof(cmd_output), output) == NULL) + pg_fatal("Could not get pg_ctl version data using %s: %s\n", + cmd, getErrorText(errno)); + + pclose(output); + + /* Remove trailing newline */ + if (strchr(cmd_output, '\n') != NULL) + *strchr(cmd_output, '\n') = '\0'; + + if (sscanf(cmd_output, "%*s %*s %d.%d", &pre_dot, &post_dot) != 2) + pg_fatal("could not get version from %s\n", cmd); + + cluster->bin_version = (pre_dot * 100 + post_dot) * 100; +} + + +/* + * 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; +} |