Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 57c081d

Browse files
committedApr 29, 2021
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 94b9cb7 commit 57c081d

File tree

3 files changed

+133
-149
lines changed

3 files changed

+133
-149
lines changed
 

‎src/bin/pg_upgrade/check.c

Lines changed: 84 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static void check_for_prepared_transactions(ClusterInfo *cluster);
2424
static void check_for_isn_and_int8_passing_mismatch(ClusterInfo *cluster);
2525
static void check_for_user_defined_postfix_ops(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);
@@ -100,6 +101,7 @@ check_and_dump_old_cluster(bool live_check)
100101
check_is_install_user(&old_cluster);
101102
check_proper_datallowconn(&old_cluster);
102103
check_for_prepared_transactions(&old_cluster);
104+
check_for_composite_data_type_usage(&old_cluster);
103105
check_for_reg_data_type_usage(&old_cluster);
104106
check_for_isn_and_int8_passing_mismatch(&old_cluster);
105107

@@ -1043,6 +1045,63 @@ check_for_tables_with_oids(ClusterInfo *cluster)
10431045
}
10441046

10451047

1048+
/*
1049+
* check_for_composite_data_type_usage()
1050+
* Check for system-defined composite types used in user tables.
1051+
*
1052+
* The OIDs of rowtypes of system catalogs and information_schema views
1053+
* can change across major versions; unlike user-defined types, we have
1054+
* no mechanism for forcing them to be the same in the new cluster.
1055+
* Hence, if any user table uses one, that's problematic for pg_upgrade.
1056+
*/
1057+
static void
1058+
check_for_composite_data_type_usage(ClusterInfo *cluster)
1059+
{
1060+
bool found;
1061+
Oid firstUserOid;
1062+
char output_path[MAXPGPATH];
1063+
char *base_query;
1064+
1065+
prep_status("Checking for system-defined composite types in user tables");
1066+
1067+
snprintf(output_path, sizeof(output_path), "tables_using_composite.txt");
1068+
1069+
/*
1070+
* Look for composite types that were made during initdb *or* belong to
1071+
* information_schema; that's important in case information_schema was
1072+
* dropped and reloaded.
1073+
*
1074+
* The cutoff OID here should match the source cluster's value of
1075+
* FirstNormalObjectId. We hardcode it rather than using that C #define
1076+
* because, if that #define is ever changed, our own version's value is
1077+
* NOT what to use. Eventually we may need a test on the source cluster's
1078+
* version to select the correct value.
1079+
*/
1080+
firstUserOid = 16384;
1081+
1082+
base_query = psprintf("SELECT t.oid FROM pg_catalog.pg_type t "
1083+
"LEFT JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid "
1084+
" WHERE typtype = 'c' AND (t.oid < %u OR nspname = 'information_schema')",
1085+
firstUserOid);
1086+
1087+
found = check_for_data_types_usage(cluster, base_query, output_path);
1088+
1089+
free(base_query);
1090+
1091+
if (found)
1092+
{
1093+
pg_log(PG_REPORT, "fatal\n");
1094+
pg_fatal("Your installation contains system-defined composite type(s) in user tables.\n"
1095+
"These type OIDs are not stable across PostgreSQL versions,\n"
1096+
"so this cluster cannot currently be upgraded. You can\n"
1097+
"drop the problem columns and restart the upgrade.\n"
1098+
"A list of the problem columns is in the file:\n"
1099+
" %s\n\n", output_path);
1100+
}
1101+
else
1102+
check_ok();
1103+
}
1104+
10461105
/*
10471106
* check_for_reg_data_type_usage()
10481107
* pg_upgrade only preserves these system values:
@@ -1057,88 +1116,36 @@ check_for_tables_with_oids(ClusterInfo *cluster)
10571116
static void
10581117
check_for_reg_data_type_usage(ClusterInfo *cluster)
10591118
{
1060-
int dbnum;
1061-
FILE *script = NULL;
1062-
bool found = false;
1119+
bool found;
10631120
char output_path[MAXPGPATH];
10641121

10651122
prep_status("Checking for reg* data types in user tables");
10661123

10671124
snprintf(output_path, sizeof(output_path), "tables_using_reg.txt");
10681125

1069-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
1070-
{
1071-
PGresult *res;
1072-
bool db_used = false;
1073-
int ntups;
1074-
int rowno;
1075-
int i_nspname,
1076-
i_relname,
1077-
i_attname;
1078-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
1079-
PGconn *conn = connectToServer(cluster, active_db->db_name);
1080-
1081-
/*
1082-
* While several relkinds don't store any data, e.g. views, they can
1083-
* be used to define data types of other columns, so we check all
1084-
* relkinds.
1085-
*/
1086-
res = executeQueryOrDie(conn,
1087-
"SELECT n.nspname, c.relname, a.attname "
1088-
"FROM pg_catalog.pg_class c, "
1089-
" pg_catalog.pg_namespace n, "
1090-
" pg_catalog.pg_attribute a, "
1091-
" pg_catalog.pg_type t "
1092-
"WHERE c.oid = a.attrelid AND "
1093-
" NOT a.attisdropped AND "
1094-
" a.atttypid = t.oid AND "
1095-
" t.typnamespace = "
1096-
" (SELECT oid FROM pg_namespace "
1097-
" WHERE nspname = 'pg_catalog') AND"
1098-
" t.typname IN ( "
1099-
/* regclass.oid is preserved, so 'regclass' is OK */
1100-
" 'regcollation', "
1101-
" 'regconfig', "
1102-
" 'regdictionary', "
1103-
" 'regnamespace', "
1104-
" 'regoper', "
1105-
" 'regoperator', "
1106-
" 'regproc', "
1107-
" 'regprocedure' "
1108-
/* regrole.oid is preserved, so 'regrole' is OK */
1109-
/* regtype.oid is preserved, so 'regtype' is OK */
1110-
" ) AND "
1111-
" c.relnamespace = n.oid AND "
1112-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
1113-
1114-
ntups = PQntuples(res);
1115-
i_nspname = PQfnumber(res, "nspname");
1116-
i_relname = PQfnumber(res, "relname");
1117-
i_attname = PQfnumber(res, "attname");
1118-
for (rowno = 0; rowno < ntups; rowno++)
1119-
{
1120-
found = true;
1121-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
1122-
pg_fatal("could not open file \"%s\": %s\n",
1123-
output_path, strerror(errno));
1124-
if (!db_used)
1125-
{
1126-
fprintf(script, "In database: %s\n", active_db->db_name);
1127-
db_used = true;
1128-
}
1129-
fprintf(script, " %s.%s.%s\n",
1130-
PQgetvalue(res, rowno, i_nspname),
1131-
PQgetvalue(res, rowno, i_relname),
1132-
PQgetvalue(res, rowno, i_attname));
1133-
}
1134-
1135-
PQclear(res);
1136-
1137-
PQfinish(conn);
1138-
}
1139-
1140-
if (script)
1141-
fclose(script);
1126+
/*
1127+
* Note: older servers will not have all of these reg* types, so we have
1128+
* to write the query like this rather than depending on casts to regtype.
1129+
*/
1130+
found = check_for_data_types_usage(cluster,
1131+
"SELECT oid FROM pg_catalog.pg_type t "
1132+
"WHERE t.typnamespace = "
1133+
" (SELECT oid FROM pg_catalog.pg_namespace "
1134+
" WHERE nspname = 'pg_catalog') "
1135+
" AND t.typname IN ( "
1136+
/* pg_class.oid is preserved, so 'regclass' is OK */
1137+
" 'regcollation', "
1138+
" 'regconfig', "
1139+
" 'regdictionary', "
1140+
" 'regnamespace', "
1141+
" 'regoper', "
1142+
" 'regoperator', "
1143+
" 'regproc', "
1144+
" 'regprocedure' "
1145+
/* pg_authid.oid is preserved, so 'regrole' is OK */
1146+
/* pg_type.oid is (mostly) preserved, so 'regtype' is OK */
1147+
" )",
1148+
output_path);
11421149

11431150
if (found)
11441151
{
@@ -1163,75 +1170,13 @@ check_for_reg_data_type_usage(ClusterInfo *cluster)
11631170
static void
11641171
check_for_jsonb_9_4_usage(ClusterInfo *cluster)
11651172
{
1166-
int dbnum;
1167-
FILE *script = NULL;
1168-
bool found = false;
11691173
char output_path[MAXPGPATH];
11701174

11711175
prep_status("Checking for incompatible \"jsonb\" data type");
11721176

11731177
snprintf(output_path, sizeof(output_path), "tables_using_jsonb.txt");
11741178

1175-
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
1176-
{
1177-
PGresult *res;
1178-
bool db_used = false;
1179-
int ntups;
1180-
int rowno;
1181-
int i_nspname,
1182-
i_relname,
1183-
i_attname;
1184-
DbInfo *active_db = &cluster->dbarr.dbs[dbnum];
1185-
PGconn *conn = connectToServer(cluster, active_db->db_name);
1186-
1187-
/*
1188-
* While several relkinds don't store any data, e.g. views, they can
1189-
* be used to define data types of other columns, so we check all
1190-
* relkinds.
1191-
*/
1192-
res = executeQueryOrDie(conn,
1193-
"SELECT n.nspname, c.relname, a.attname "
1194-
"FROM pg_catalog.pg_class c, "
1195-
" pg_catalog.pg_namespace n, "
1196-
" pg_catalog.pg_attribute a "
1197-
"WHERE c.oid = a.attrelid AND "
1198-
" NOT a.attisdropped AND "
1199-
" a.atttypid = 'pg_catalog.jsonb'::pg_catalog.regtype AND "
1200-
" c.relnamespace = n.oid AND "
1201-
/* exclude possible orphaned temp tables */
1202-
" n.nspname !~ '^pg_temp_' AND "
1203-
" n.nspname NOT IN ('pg_catalog', 'information_schema')");
1204-
1205-
ntups = PQntuples(res);
1206-
i_nspname = PQfnumber(res, "nspname");
1207-
i_relname = PQfnumber(res, "relname");
1208-
i_attname = PQfnumber(res, "attname");
1209-
for (rowno = 0; rowno < ntups; rowno++)
1210-
{
1211-
found = true;
1212-
if (script == NULL && (script = fopen_priv(output_path, "w")) == NULL)
1213-
pg_fatal("could not open file \"%s\": %s\n",
1214-
output_path, strerror(errno));
1215-
if (!db_used)
1216-
{
1217-
fprintf(script, "In database: %s\n", active_db->db_name);
1218-
db_used = true;
1219-
}
1220-
fprintf(script, " %s.%s.%s\n",
1221-
PQgetvalue(res, rowno, i_nspname),
1222-
PQgetvalue(res, rowno, i_relname),
1223-
PQgetvalue(res, rowno, i_attname));
1224-
}
1225-
1226-
PQclear(res);
1227-
1228-
PQfinish(conn);
1229-
}
1230-
1231-
if (script)
1232-
fclose(script);
1233-
1234-
if (found)
1179+
if (check_for_data_type_usage(cluster, "pg_catalog.jsonb", output_path))
12351180
{
12361181
pg_log(PG_REPORT, "fatal\n");
12371182
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
@@ -440,6 +440,12 @@ unsigned int str2uint(const char *str);
440440

441441
/* version.c */
442442

443+
bool check_for_data_types_usage(ClusterInfo *cluster,
444+
const char *base_query,
445+
const char *output_path);
446+
bool check_for_data_type_usage(ClusterInfo *cluster,
447+
const char *typename,
448+
const char *output_path);
443449
void new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster,
444450
bool check_mode);
445451
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
@@ -97,17 +97,22 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)
9797

9898

9999
/*
100-
* check_for_data_type_usage
101-
* Detect whether there are any stored columns depending on the given type
100+
* check_for_data_types_usage()
101+
* Detect whether there are any stored columns depending on given type(s)
102102
*
103103
* If so, write a report to the given file name, and return true.
104104
*
105-
* We check for the type in tables, matviews, and indexes, but not views;
105+
* base_query should be a SELECT yielding a single column named "oid",
106+
* containing the pg_type OIDs of one or more types that are known to have
107+
* inconsistent on-disk representations across server versions.
108+
*
109+
* We check for the type(s) in tables, matviews, and indexes, but not views;
106110
* there's no storage involved in a view.
107111
*/
108-
static bool
109-
check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
110-
char *output_path)
112+
bool
113+
check_for_data_types_usage(ClusterInfo *cluster,
114+
const char *base_query,
115+
const char *output_path)
111116
{
112117
bool found = false;
113118
FILE *script = NULL;
@@ -127,16 +132,16 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
127132
i_attname;
128133

129134
/*
130-
* The type of interest might be wrapped in a domain, array,
135+
* The type(s) of interest might be wrapped in a domain, array,
131136
* composite, or range, and these container types can be nested (to
132137
* varying extents depending on server version, but that's not of
133138
* concern here). To handle all these cases we need a recursive CTE.
134139
*/
135140
initPQExpBuffer(&querybuf);
136141
appendPQExpBuffer(&querybuf,
137142
"WITH RECURSIVE oids AS ( "
138-
/* the target type itself */
139-
" SELECT '%s'::pg_catalog.regtype AS oid "
143+
/* start with the type(s) returned by base_query */
144+
" %s "
140145
" UNION ALL "
141146
" SELECT * FROM ( "
142147
/* inner WITH because we can only reference the CTE once */
@@ -154,7 +159,7 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
154159
" c.oid = a.attrelid AND "
155160
" NOT a.attisdropped AND "
156161
" a.atttypid = x.oid ",
157-
typename);
162+
base_query);
158163

159164
/* Ranges were introduced in 9.2 */
160165
if (GET_MAJOR_VERSION(cluster->major_version) >= 902)
@@ -222,6 +227,34 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
222227
return found;
223228
}
224229

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

226259
/*
227260
* old_9_3_check_for_line_data_type_usage()

0 commit comments

Comments
 (0)
Failed to load comments.