Skip to content

Commit ba86371

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 adbcd3e commit ba86371

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
@@ -24,6 +24,7 @@ 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);
2626
static void check_for_tables_with_oids(ClusterInfo *cluster);
27+
static void check_for_composite_data_type_usage(ClusterInfo *cluster);
2728
static void check_for_reg_data_type_usage(ClusterInfo *cluster);
2829
static void check_for_jsonb_9_4_usage(ClusterInfo *cluster);
2930
static void check_for_pg_role_prefix(ClusterInfo *cluster);
@@ -99,6 +100,7 @@ check_and_dump_old_cluster(bool live_check)
99100
check_is_install_user(&old_cluster);
100101
check_proper_datallowconn(&old_cluster);
101102
check_for_prepared_transactions(&old_cluster);
103+
check_for_composite_data_type_usage(&old_cluster);
102104
check_for_reg_data_type_usage(&old_cluster);
103105
check_for_isn_and_int8_passing_mismatch(&old_cluster);
104106

@@ -1001,6 +1003,63 @@ check_for_tables_with_oids(ClusterInfo *cluster)
10011003
}
10021004

10031005

1006+
/*
1007+
* check_for_composite_data_type_usage()
1008+
* Check for system-defined composite types used in user tables.
1009+
*
1010+
* The OIDs of rowtypes of system catalogs and information_schema views
1011+
* can change across major versions; unlike user-defined types, we have
1012+
* no mechanism for forcing them to be the same in the new cluster.
1013+
* Hence, if any user table uses one, that's problematic for pg_upgrade.
1014+
*/
1015+
static void
1016+
check_for_composite_data_type_usage(ClusterInfo *cluster)
1017+
{
1018+
bool found;
1019+
Oid firstUserOid;
1020+
char output_path[MAXPGPATH];
1021+
char *base_query;
1022+
1023+
prep_status("Checking for system-defined composite types in user tables");
1024+
1025+
snprintf(output_path, sizeof(output_path), "tables_using_composite.txt");
1026+
1027+
/*
1028+
* Look for composite types that were made during initdb *or* belong to
1029+
* information_schema; that's important in case information_schema was
1030+
* dropped and reloaded.
1031+
*
1032+
* The cutoff OID here should match the source cluster's value of
1033+
* FirstNormalObjectId. We hardcode it rather than using that C #define
1034+
* because, if that #define is ever changed, our own version's value is
1035+
* NOT what to use. Eventually we may need a test on the source cluster's
1036+
* version to select the correct value.
1037+
*/
1038+
firstUserOid = 16384;
1039+
1040+
base_query = psprintf("SELECT t.oid FROM pg_catalog.pg_type t "
1041+
"LEFT JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid "
1042+
" WHERE typtype = 'c' AND (t.oid < %u OR nspname = 'information_schema')",
1043+
firstUserOid);
1044+
1045+
found = check_for_data_types_usage(cluster, base_query, output_path);
1046+
1047+
free(base_query);
1048+
1049+
if (found)
1050+
{
1051+
pg_log(PG_REPORT, "fatal\n");
1052+
pg_fatal("Your installation contains system-defined composite type(s) in user tables.\n"
1053+
"These type OIDs are not stable across PostgreSQL versions,\n"
1054+
"so this cluster cannot currently be upgraded. You can\n"
1055+
"drop the problem columns and restart the upgrade.\n"
1056+
"A list of the problem columns is in the file:\n"
1057+
" %s\n\n", output_path);
1058+
}
1059+
else
1060+
check_ok();
1061+
}
1062+
10041063
/*
10051064
* check_for_reg_data_type_usage()
10061065
* pg_upgrade only preserves these system values:
@@ -1015,87 +1074,36 @@ check_for_tables_with_oids(ClusterInfo *cluster)
10151074
static void
10161075
check_for_reg_data_type_usage(ClusterInfo *cluster)
10171076
{
1018-
int dbnum;
1019-
FILE *script = NULL;
1020-
bool found = false;
1077+
bool found;
10211078
char output_path[MAXPGPATH];
10221079

10231080
prep_status("Checking for reg* data types in user tables");
10241081

10251082
snprintf(output_path, sizeof(output_path), "tables_using_reg.txt");
10261083

1027-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
1028-
{
1029-
PGresult *res;
1030-
bool db_used = false;
1031-
int ntups;
1032-
int rowno;
1033-
int i_nspname,
1034-
i_relname,
1035-
i_attname;
1036-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
1037-
PGconn *conn = connectToServer(cluster, active_db->db_name);
1038-
1039-
/*
1040-
* While several relkinds don't store any data, e.g. views, they can
1041-
* be used to define data types of other columns, so we check all
1042-
* relkinds.
1043-
*/
1044-
res = executeQueryOrDie(conn,
1045-
"SELECT n.nspname, c.relname, a.attname "
1046-
"FROM pg_catalog.pg_class c, "
1047-
" pg_catalog.pg_namespace n, "
1048-
" pg_catalog.pg_attribute a, "
1049-
" pg_catalog.pg_type t "
1050-
"WHERE c.oid = a.attrelid AND "
1051-
" NOT a.attisdropped AND "
1052-
" a.atttypid = t.oid AND "
1053-
" t.typnamespace = "
1054-
" (SELECT oid FROM pg_namespace "
1055-
" WHERE nspname = 'pg_catalog') AND"
1056-
" t.typname IN ( "
1057-
/* regclass.oid is preserved, so 'regclass' is OK */
1058-
" 'regconfig', "
1059-
" 'regdictionary', "
1060-
" 'regnamespace', "
1061-
" 'regoper', "
1062-
" 'regoperator', "
1063-
" 'regproc', "
1064-
" 'regprocedure' "
1065-
/* regrole.oid is preserved, so 'regrole' is OK */
1066-
/* regtype.oid is preserved, so 'regtype' is OK */
1067-
" ) AND "
1068-
" c.relnamespace = n.oid AND "
1069-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
1070-
1071-
ntups = PQntuples(res);
1072-
i_nspname = PQfnumber(res, "nspname");
1073-
i_relname = PQfnumber(res, "relname");
1074-
i_attname = PQfnumber(res, "attname");
1075-
for (rowno = 0; rowno < ntups; rowno++)
1076-
{
1077-
found = true;
1078-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
1079-
pg_fatal("could not open file \"%s\": %s\n",
1080-
output_path, strerror(errno));
1081-
if (!db_used)
1082-
{
1083-
fprintf(script, "Database: %s\n", active_db->db_name);
1084-
db_used = true;
1085-
}
1086-
fprintf(script, " %s.%s.%s\n",
1087-
PQgetvalue(res, rowno, i_nspname),
1088-
PQgetvalue(res, rowno, i_relname),
1089-
PQgetvalue(res, rowno, i_attname));
1090-
}
1091-
1092-
PQclear(res);
1093-
1094-
PQfinish(conn);
1095-
}
1096-
1097-
if (script)
1098-
fclose(script);
1084+
/*
1085+
* Note: older servers will not have all of these reg* types, so we have
1086+
* to write the query like this rather than depending on casts to regtype.
1087+
*/
1088+
found = check_for_data_types_usage(cluster,
1089+
"SELECT oid FROM pg_catalog.pg_type t "
1090+
"WHERE t.typnamespace = "
1091+
" (SELECT oid FROM pg_catalog.pg_namespace "
1092+
" WHERE nspname = 'pg_catalog') "
1093+
" AND t.typname IN ( "
1094+
/* pg_class.oid is preserved, so 'regclass' is OK */
1095+
" 'regcollation', "
1096+
" 'regconfig', "
1097+
" 'regdictionary', "
1098+
" 'regnamespace', "
1099+
" 'regoper', "
1100+
" 'regoperator', "
1101+
" 'regproc', "
1102+
" 'regprocedure' "
1103+
/* pg_authid.oid is preserved, so 'regrole' is OK */
1104+
/* pg_type.oid is (mostly) preserved, so 'regtype' is OK */
1105+
" )",
1106+
output_path);
10991107

11001108
if (found)
11011109
{
@@ -1120,75 +1128,13 @@ check_for_reg_data_type_usage(ClusterInfo *cluster)
11201128
static void
11211129
check_for_jsonb_9_4_usage(ClusterInfo *cluster)
11221130
{
1123-
int dbnum;
1124-
FILE *script = NULL;
1125-
bool found = false;
11261131
char output_path[MAXPGPATH];
11271132

11281133
prep_status("Checking for incompatible \"jsonb\" data type");
11291134

11301135
snprintf(output_path, sizeof(output_path), "tables_using_jsonb.txt");
11311136

1132-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
1133-
{
1134-
PGresult *res;
1135-
bool db_used = false;
1136-
int ntups;
1137-
int rowno;
1138-
int i_nspname,
1139-
i_relname,
1140-
i_attname;
1141-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
1142-
PGconn *conn = connectToServer(cluster, active_db->db_name);
1143-
1144-
/*
1145-
* While several relkinds don't store any data, e.g. views, they can
1146-
* be used to define data types of other columns, so we check all
1147-
* relkinds.
1148-
*/
1149-
res = executeQueryOrDie(conn,
1150-
"SELECT n.nspname, c.relname, a.attname "
1151-
"FROM pg_catalog.pg_class c, "
1152-
" pg_catalog.pg_namespace n, "
1153-
" pg_catalog.pg_attribute a "
1154-
"WHERE c.oid = a.attrelid AND "
1155-
" NOT a.attisdropped AND "
1156-
" a.atttypid = 'pg_catalog.jsonb'::pg_catalog.regtype AND "
1157-
" c.relnamespace = n.oid AND "
1158-
/* exclude possible orphaned temp tables */
1159-
" n.nspname !~ '^pg_temp_' AND "
1160-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
1161-
1162-
ntups = PQntuples(res);
1163-
i_nspname = PQfnumber(res, "nspname");
1164-
i_relname = PQfnumber(res, "relname");
1165-
i_attname = PQfnumber(res, "attname");
1166-
for (rowno = 0; rowno < ntups; rowno++)
1167-
{
1168-
found = true;
1169-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
1170-
pg_fatal("could not open file \"%s\": %s\n",
1171-
output_path, strerror(errno));
1172-
if (!db_used)
1173-
{
1174-
fprintf(script, "Database: %s\n", active_db->db_name);
1175-
db_used = true;
1176-
}
1177-
fprintf(script, " %s.%s.%s\n",
1178-
PQgetvalue(res, rowno, i_nspname),
1179-
PQgetvalue(res, rowno, i_relname),
1180-
PQgetvalue(res, rowno, i_attname));
1181-
}
1182-
1183-
PQclear(res);
1184-
1185-
PQfinish(conn);
1186-
}
1187-
1188-
if (script)
1189-
fclose(script);
1190-
1191-
if (found)
1137+
if (check_for_data_type_usage(cluster, "pg_catalog.jsonb", output_path))
11921138
{
11931139
pg_log(PG_REPORT, "fatal\n");
11941140
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
@@ -451,6 +451,12 @@ void pg_putenv(const char *var, const char *val);
451451

452452
/* version.c */
453453

454+
bool check_for_data_types_usage(ClusterInfo *cluster,
455+
const char *base_query,
456+
const char *output_path);
457+
bool check_for_data_type_usage(ClusterInfo *cluster,
458+
const char *typename,
459+
const char *output_path);
454460
void new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster,
455461
bool check_mode);
456462
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)