Skip to content

Commit 404946d

Browse files
committed
Fix some more omissions in pg_upgrade's tests for non-upgradable types.
Commits 29aeda6 et al closed up some oversights involving not checking for non-upgradable types within container types, such as arrays and ranges. However, I only looked at version.c, failing to notice that there were substantially-equivalent tests in check.c. (The division of responsibility between those files is less than clear...) In addition, because genbki.pl does not guarantee that auto-generated rowtype OIDs will hold still across versions, we need to consider that the composite type associated with a system catalog or view is non-upgradable. It seems unlikely that someone would have a user column declared that way, but if they did, trying to read it in another PG version would likely draw "no such pg_type OID" failures, thanks to the type OID embedded in composite Datums. To support the composite and reg*-type cases, extend the recursive query that does the search to allow any base query that returns a column of pg_type OIDs, rather than limiting it to exactly one starting type. As before, back-patch to all supported branches. Discussion: https://postgr.es/m/2798740.1619622555@sss.pgh.pa.us
1 parent 7cd5420 commit 404946d

File tree

3 files changed

+133
-148
lines changed

3 files changed

+133
-148
lines changed

src/bin/pg_upgrade/check.c

Lines changed: 84 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ static void check_is_install_user(ClusterInfo *cluster);
2323
static void check_proper_datallowconn(ClusterInfo *cluster);
2424
static void check_for_prepared_transactions(ClusterInfo *cluster);
2525
static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster);
26+
static void check_for_composite_data_type_usage(ClusterInfo *cluster);
2627
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
2728
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
2829
static void check_for_pg_role_prefix(ClusterInfo *cluster);
@@ -98,6 +99,7 @@ check_and_dump_old_cluster(bool live_check)
9899
check_is_install_user(&old_cluster);
99100
check_proper_datallowconn(&old_cluster);
100101
check_for_prepared_transactions(&old_cluster);
102+
check_for_composite_data_type_usage(&old_cluster);
101103
check_for_reg_data_type_usage(&old_cluster);
102104
check_for_isn_and_int8_passing_mismatch(&old_cluster);
103105

@@ -897,6 +899,63 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster)
897899
}
898900

899901

902+
/*
903+
* check_for_composite_data_type_usage()
904+
* Check for system-defined composite types used in user tables.
905+
*
906+
* The OIDs of rowtypes of system catalogs and information_schema views
907+
* can change across major versions; unlike user-defined types, we have
908+
* no mechanism for forcing them to be the same in the new cluster.
909+
* Hence, if any user table uses one, that's problematic for pg_upgrade.
910+
*/
911+
static void
912+
check_for_composite_data_type_usage(ClusterInfo *cluster)
913+
{
914+
bool found;
915+
Oid firstUserOid;
916+
char output_path[MAXPGPATH];
917+
char *base_query;
918+
919+
prep_status("Checking for system-defined composite types in user tables");
920+
921+
snprintf(output_path, sizeof(output_path), "tables_using_composite.txt");
922+
923+
/*
924+
* Look for composite types that were made during initdb *or* belong to
925+
* information_schema; that's important in case information_schema was
926+
* dropped and reloaded.
927+
*
928+
* The cutoff OID here should match the source cluster's value of
929+
* FirstNormalObjectId. We hardcode it rather than using that C #define
930+
* because, if that #define is ever changed, our own version's value is
931+
* NOT what to use. Eventually we may need a test on the source cluster's
932+
* version to select the correct value.
933+
*/
934+
firstUserOid = 16384;
935+
936+
base_query = psprintf("SELECT t.oid FROM pg_catalog.pg_type t "
937+
"LEFT JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid "
938+
" WHERE typtype = 'c' AND (t.oid < %u OR nspname = 'information_schema')",
939+
firstUserOid);
940+
941+
found = check_for_data_types_usage(cluster, base_query, output_path);
942+
943+
free(base_query);
944+
945+
if (found)
946+
{
947+
pg_log(PG_REPORT, "fatal\n");
948+
pg_fatal("Your installation contains system-defined composite type(s) in user tables.\n"
949+
"These type OIDs are not stable across PostgreSQL versions,\n"
950+
"so this cluster cannot currently be upgraded. You can\n"
951+
"drop the problem columns and restart the upgrade.\n"
952+
"A list of the problem columns is in the file:\n"
953+
" %s\n\n", output_path);
954+
}
955+
else
956+
check_ok();
957+
}
958+
900959
/*
901960
* check_for_reg_data_type_usage()
902961
* pg_upgrade only preserves these system values:
@@ -911,87 +970,36 @@ check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster)
911970
static void
912971
check_for_reg_data_type_usage(ClusterInfo *cluster)
913972
{
914-
int dbnum;
915-
FILE *script = NULL;
916-
bool found = false;
973+
bool found;
917974
char output_path[MAXPGPATH];
918975

919976
prep_status("Checking for reg* data types in user tables");
920977

921978
snprintf(output_path, sizeof(output_path), "tables_using_reg.txt");
922979

923-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
924-
{
925-
PGresult *res;
926-
bool db_used = false;
927-
int ntups;
928-
int rowno;
929-
int i_nspname,
930-
i_relname,
931-
i_attname;
932-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
933-
PGconn *conn = connectToServer(cluster, active_db->db_name);
934-
935-
/*
936-
* While several relkinds don't store any data, e.g. views, they can
937-
* be used to define data types of other columns, so we check all
938-
* relkinds.
939-
*/
940-
res = executeQueryOrDie(conn,
941-
"SELECT n.nspname, c.relname, a.attname "
942-
"FROM pg_catalog.pg_class c, "
943-
" pg_catalog.pg_namespace n, "
944-
" pg_catalog.pg_attribute a, "
945-
" pg_catalog.pg_type t "
946-
"WHERE c.oid = a.attrelid AND "
947-
" NOT a.attisdropped AND "
948-
" a.atttypid = t.oid AND "
949-
" t.typnamespace = "
950-
" (SELECT oid FROM pg_namespace "
951-
" WHERE nspname = 'pg_catalog') AND"
952-
" t.typname IN ( "
953-
/* regclass.oid is preserved, so 'regclass' is OK */
954-
" 'regconfig', "
955-
" 'regdictionary', "
956-
" 'regnamespace', "
957-
" 'regoper', "
958-
" 'regoperator', "
959-
" 'regproc', "
960-
" 'regprocedure' "
961-
/* regrole.oid is preserved, so 'regrole' is OK */
962-
/* regtype.oid is preserved, so 'regtype' is OK */
963-
" ) AND "
964-
" c.relnamespace = n.oid AND "
965-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
966-
967-
ntups = PQntuples(res);
968-
i_nspname = PQfnumber(res, "nspname");
969-
i_relname = PQfnumber(res, "relname");
970-
i_attname = PQfnumber(res, "attname");
971-
for (rowno = 0; rowno < ntups; rowno++)
972-
{
973-
found = true;
974-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
975-
pg_fatal("could not open file \"%s\": %s\n",
976-
output_path, strerror(errno));
977-
if (!db_used)
978-
{
979-
fprintf(script, "Database: %s\n", active_db->db_name);
980-
db_used = true;
981-
}
982-
fprintf(script, " %s.%s.%s\n",
983-
PQgetvalue(res, rowno, i_nspname),
984-
PQgetvalue(res, rowno, i_relname),
985-
PQgetvalue(res, rowno, i_attname));
986-
}
987-
988-
PQclear(res);
989-
990-
PQfinish(conn);
991-
}
992-
993-
if (script)
994-
fclose(script);
980+
/*
981+
* Note: older servers will not have all of these reg* types, so we have
982+
* to write the query like this rather than depending on casts to regtype.
983+
*/
984+
found = check_for_data_types_usage(cluster,
985+
"SELECT oid FROM pg_catalog.pg_type t "
986+
"WHERE t.typnamespace = "
987+
" (SELECT oid FROM pg_catalog.pg_namespace "
988+
" WHERE nspname = 'pg_catalog') "
989+
" AND t.typname IN ( "
990+
/* pg_class.oid is preserved, so 'regclass' is OK */
991+
" 'regcollation', "
992+
" 'regconfig', "
993+
" 'regdictionary', "
994+
" 'regnamespace', "
995+
" 'regoper', "
996+
" 'regoperator', "
997+
" 'regproc', "
998+
" 'regprocedure' "
999+
/* pg_authid.oid is preserved, so 'regrole' is OK */
1000+
/* pg_type.oid is (mostly) preserved, so 'regtype' is OK */
1001+
" )",
1002+
output_path);
9951003

9961004
if (found)
9971005
{
@@ -1016,75 +1024,13 @@ check_for_reg_data_type_usage(ClusterInfo *cluster)
10161024
static void
10171025
check_for_jsonb_9_4_usage(ClusterInfo *cluster)
10181026
{
1019-
int dbnum;
1020-
FILE *script = NULL;
1021-
bool found = false;
10221027
char output_path[MAXPGPATH];
10231028

10241029
prep_status("Checking for incompatible \"jsonb\" data type");
10251030

10261031
snprintf(output_path, sizeof(output_path), "tables_using_jsonb.txt");
10271032

1028-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
1029-
{
1030-
PGresult *res;
1031-
bool db_used = false;
1032-
int ntups;
1033-
int rowno;
1034-
int i_nspname,
1035-
i_relname,
1036-
i_attname;
1037-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
1038-
PGconn *conn = connectToServer(cluster, active_db->db_name);
1039-
1040-
/*
1041-
* While several relkinds don't store any data, e.g. views, they can
1042-
* be used to define data types of other columns, so we check all
1043-
* relkinds.
1044-
*/
1045-
res = executeQueryOrDie(conn,
1046-
"SELECT n.nspname, c.relname, a.attname "
1047-
"FROM pg_catalog.pg_class c, "
1048-
" pg_catalog.pg_namespace n, "
1049-
" pg_catalog.pg_attribute a "
1050-
"WHERE c.oid = a.attrelid AND "
1051-
" NOT a.attisdropped AND "
1052-
" a.atttypid = 'pg_catalog.jsonb'::pg_catalog.regtype AND "
1053-
" c.relnamespace = n.oid AND "
1054-
/* exclude possible orphaned temp tables */
1055-
" n.nspname !~ '^pg_temp_' AND "
1056-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
1057-
1058-
ntups = PQntuples(res);
1059-
i_nspname = PQfnumber(res, "nspname");
1060-
i_relname = PQfnumber(res, "relname");
1061-
i_attname = PQfnumber(res, "attname");
1062-
for (rowno = 0; rowno < ntups; rowno++)
1063-
{
1064-
found = true;
1065-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
1066-
pg_fatal("could not open file \"%s\": %s\n",
1067-
output_path, strerror(errno));
1068-
if (!db_used)
1069-
{
1070-
fprintf(script, "Database: %s\n", active_db->db_name);
1071-
db_used = true;
1072-
}
1073-
fprintf(script, " %s.%s.%s\n",
1074-
PQgetvalue(res, rowno, i_nspname),
1075-
PQgetvalue(res, rowno, i_relname),
1076-
PQgetvalue(res, rowno, i_attname));
1077-
}
1078-
1079-
PQclear(res);
1080-
1081-
PQfinish(conn);
1082-
}
1083-
1084-
if (script)
1085-
fclose(script);
1086-
1087-
if (found)
1033+
if (check_for_data_type_usage(cluster, "pg_catalog.jsonb", output_path))
10881034
{
10891035
pg_log(PG_REPORT, "fatal\n");
10901036
pg_fatal("Your installation contains the \"jsonb\" data type in user tables.\n"

src/bin/pg_upgrade/pg_upgrade.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ void pg_putenv(const char *var, const char *val);
441441

442442
/* version.c */
443443

444+
bool check_for_data_types_usage(ClusterInfo *cluster,
445+
const char *base_query,
446+
const char *output_path);
447+
bool check_for_data_type_usage(ClusterInfo *cluster,
448+
const char *typename,
449+
const char *output_path);
444450
void new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster,
445451
bool check_mode);
446452
void old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster);

src/bin/pg_upgrade/version.c

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,17 +100,22 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)
100100

101101

102102
/*
103-
* check_for_data_type_usage
104-
* Detect whether there are any stored columns depending on the given type
103+
* check_for_data_types_usage()
104+
* Detect whether there are any stored columns depending on given type(s)
105105
*
106106
* If so, write a report to the given file name, and return true.
107107
*
108-
* We check for the type in tables, matviews, and indexes, but not views;
108+
* base_query should be a SELECT yielding a single column named "oid",
109+
* containing the pg_type OIDs of one or more types that are known to have
110+
* inconsistent on-disk representations across server versions.
111+
*
112+
* We check for the type(s) in tables, matviews, and indexes, but not views;
109113
* there's no storage involved in a view.
110114
*/
111-
static bool
112-
check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
113-
char *output_path)
115+
bool
116+
check_for_data_types_usage(ClusterInfo *cluster,
117+
const char *base_query,
118+
const char *output_path)
114119
{
115120
bool found = false;
116121
FILE *script = NULL;
@@ -130,16 +135,16 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
130135
i_attname;
131136

132137
/*
133-
* The type of interest might be wrapped in a domain, array,
138+
* The type(s) of interest might be wrapped in a domain, array,
134139
* composite, or range, and these container types can be nested (to
135140
* varying extents depending on server version, but that's not of
136141
* concern here). To handle all these cases we need a recursive CTE.
137142
*/
138143
initPQExpBuffer(&querybuf);
139144
appendPQExpBuffer(&querybuf,
140145
"WITH RECURSIVE oids AS ( "
141-
/* the target type itself */
142-
" SELECT '%s'::pg_catalog.regtype AS oid "
146+
/* start with the type(s) returned by base_query */
147+
" %s "
143148
" UNION ALL "
144149
" SELECT * FROM ( "
145150
/* inner WITH because we can only reference the CTE once */
@@ -157,7 +162,7 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
157162
" c.oid = a.attrelid AND "
158163
" NOT a.attisdropped AND "
159164
" a.atttypid = x.oid ",
160-
typename);
165+
base_query);
161166

162167
/* Ranges came in in 9.2 */
163168
if (GET_MAJOR_VERSION(cluster->major_version) >= 902)
@@ -225,6 +230,34 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
225230
return found;
226231
}
227232

233+
/*
234+
* check_for_data_type_usage()
235+
* Detect whether there are any stored columns depending on the given type
236+
*
237+
* If so, write a report to the given file name, and return true.
238+
*
239+
* typename should be a fully qualified type name. This is just a
240+
* trivial wrapper around check_for_data_types_usage() to convert a
241+
* type name into a base query.
242+
*/
243+
bool
244+
check_for_data_type_usage(ClusterInfo *cluster,
245+
const char *typename,
246+
const char *output_path)
247+
{
248+
bool found;
249+
char *base_query;
250+
251+
base_query = psprintf("SELECT '%s'::pg_catalog.regtype AS oid",
252+
typename);
253+
254+
found = check_for_data_types_usage(cluster, base_query, output_path);
255+
256+
free(base_query);
257+
258+
return found;
259+
}
260+
228261

229262
/*
230263
* old_9_3_check_for_line_data_type_usage()

0 commit comments

Comments
 (0)