pg_upgrade fails to detect unsupported arrays and ranges - Mailing list pgsql-hackers

From Tom Lane
Subject pg_upgrade fails to detect unsupported arrays and ranges
Date
Msg-id 31473.1573412838@sss.pgh.pa.us
Whole thread Raw
Responses Re: pg_upgrade fails to detect unsupported arrays and ranges
List pgsql-hackers
While composing the release note entry for commits 8d48e6a72 et al
(handle recursive type dependencies while checking for unsupported
types in pg_upgrade), I realized that there's a huge hole in
pg_upgrade's test for such cases.  It looks for domains containing
the unsupported type, and for composites containing it, but not
for arrays or ranges containing it.  It's definitely possible to
create tables containing arrays of lines, or arrays of composites
containing line, etc etc.  A range over line is harder for lack of
a btree opclass, but a range over sql_identifier is possible.

The attached patches fix this.  0001 refactors the code in question
so that we have only one copy not three-and-growing.  The only
difference between the three copies was that one case didn't bother
to search indexes, but I judged that that wasn't an optimization we
need to preserve.  (Note: this patch is shown with --ignore-space-change
to make it more reviewable, but I did re-pgindent the code.)  Then
0002 actually adds the array and range cases.

Although this is a really straightforward patch and I've tested it
against appropriate old versions (9.1 and 9.2), I'm very hesitant
to shove it in so soon before a release wrap.  Should I do that, or
let it wait till after the wrap?

            regards, tom lane

diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index 9deb53c..0dff96b 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -97,27 +97,27 @@ new_9_0_populate_pg_largeobject_metadata(ClusterInfo *cluster, bool check_mode)


 /*
- * old_9_3_check_for_line_data_type_usage()
- *    9.3 -> 9.4
- *    Fully implement the 'line' data type in 9.4, which previously returned
- *    "not enabled" by default and was only functionally enabled with a
- *    compile-time switch;  9.4 "line" has different binary and text
- *    representation formats;  checks tables and indexes.
+ * check_for_data_type_usage
+ *    Detect whether there are any stored columns depending on the given type
+ *
+ * If so, write a report to the given file name, and return true.
+ *
+ * We check for the type in tables, matviews, and indexes, but not views;
+ * there's no storage involved in a view.
  */
-void
-old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
+static bool
+check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
+                          char *output_path)
 {
-    int            dbnum;
-    FILE       *script = NULL;
     bool        found = false;
-    char        output_path[MAXPGPATH];
-
-    prep_status("Checking for incompatible \"line\" data type");
-
-    snprintf(output_path, sizeof(output_path), "tables_using_line.txt");
+    FILE       *script = NULL;
+    int            dbnum;

     for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
     {
+        DbInfo       *active_db = &cluster->dbarr.dbs[dbnum];
+        PGconn       *conn = connectToServer(cluster, active_db->db_name);
+        PQExpBufferData querybuf;
         PGresult   *res;
         bool        db_used = false;
         int            ntups;
@@ -125,26 +125,26 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
         int            i_nspname,
                     i_relname,
                     i_attname;
-        DbInfo       *active_db = &cluster->dbarr.dbs[dbnum];
-        PGconn       *conn = connectToServer(cluster, active_db->db_name);

         /*
-         * The pg_catalog.line type may be wrapped in a domain or composite
-         * type, or both (9.3 did not allow domains on composite types, but
-         * there may be multi-level composite type). To detect these cases
-         * we need a recursive CTE.
+         * The type of interest might be wrapped in a domain, array,
+         * composite, or range, and these container types can be nested (to
+         * varying extents depending on server version, but that's not of
+         * concern here).  To handle all these cases we need a recursive CTE.
          */
-        res = executeQueryOrDie(conn,
+        initPQExpBuffer(&querybuf);
+        appendPQExpBuffer(&querybuf,
                           "WITH RECURSIVE oids AS ( "
-        /* the pg_catalog.line type itself */
-                                "    SELECT 'pg_catalog.line'::pg_catalog.regtype AS oid "
+        /* the target type itself */
+                          "    SELECT '%s'::pg_catalog.regtype AS oid "
                           "    UNION ALL "
                           "    SELECT * FROM ( "
-        /* domains on the type */
+        /* inner WITH because we can only reference the CTE once */
                           "        WITH x AS (SELECT oid FROM oids) "
+        /* domains on any type selected so far */
                           "            SELECT t.oid FROM pg_catalog.pg_type t, x WHERE typbasetype = x.oid AND typtype
='d' " 
-                                "            UNION "
-        /* composite types containing the type */
+                          "            UNION ALL "
+        /* composite types containing any type selected so far */
                           "            SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_class c,
pg_catalog.pg_attributea, x " 
                           "            WHERE t.typtype = 'c' AND "
                           "                  t.oid = c.reltype AND "
@@ -153,6 +153,7 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
                           "                  a.atttypid = x.oid "
                           "    ) foo "
                           ") "
+        /* now look for stored columns of any such type */
                           "SELECT n.nspname, c.relname, a.attname "
                           "FROM    pg_catalog.pg_class c, "
                           "        pg_catalog.pg_namespace n, "
@@ -168,7 +169,10 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
         /* exclude possible orphaned temp tables */
                           "        n.nspname !~ '^pg_temp_' AND "
                           "        n.nspname !~ '^pg_toast_temp_' AND "
-                                "        n.nspname NOT IN ('pg_catalog', 'information_schema')");
+                          "        n.nspname NOT IN ('pg_catalog', 'information_schema')",
+                          typename);
+
+        res = executeQueryOrDie(conn, "%s", querybuf.data);

         ntups = PQntuples(res);
         i_nspname = PQfnumber(res, "nspname");
@@ -193,13 +197,36 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)

         PQclear(res);

+        termPQExpBuffer(&querybuf);
+
         PQfinish(conn);
     }

     if (script)
         fclose(script);

-    if (found)
+    return found;
+}
+
+
+/*
+ * old_9_3_check_for_line_data_type_usage()
+ *    9.3 -> 9.4
+ *    Fully implement the 'line' data type in 9.4, which previously returned
+ *    "not enabled" by default and was only functionally enabled with a
+ *    compile-time switch; as of 9.4 "line" has a different on-disk
+ *    representation format.
+ */
+void
+old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
+{
+    char        output_path[MAXPGPATH];
+
+    prep_status("Checking for incompatible \"line\" data type");
+
+    snprintf(output_path, sizeof(output_path), "tables_using_line.txt");
+
+    if (check_for_data_type_usage(cluster, "pg_catalog.line", output_path))
     {
         pg_log(PG_REPORT, "fatal\n");
         pg_fatal("Your installation contains the \"line\" data type in user tables.  This\n"
@@ -226,105 +253,17 @@ old_9_3_check_for_line_data_type_usage(ClusterInfo *cluster)
  *    mid-upgrade.  Worse, if there's a matview with such a column, the
  *    DDL reload will silently change it to "text" which won't match the
  *    on-disk storage (which is like "cstring").  So we *must* reject that.
- *    Also check composite types and domains on the "unknwown" type (even
- *    combinations of both), in case they are used for table columns.
- *    We needn't check indexes, because "unknown" has no opclasses.
  */
 void
 old_9_6_check_for_unknown_data_type_usage(ClusterInfo *cluster)
 {
-    int            dbnum;
-    FILE       *script = NULL;
-    bool        found = false;
     char        output_path[MAXPGPATH];

     prep_status("Checking for invalid \"unknown\" user columns");

     snprintf(output_path, sizeof(output_path), "tables_using_unknown.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);
-
-        /*
-         * The pg_catalog.unknown type may be wrapped in a domain or composite
-         * type, or both (9.3 did not allow domains on composite types, but
-         * there may be multi-level composite type). To detect these cases
-         * we need a recursive CTE.
-         */
-        res = executeQueryOrDie(conn,
-                                "WITH RECURSIVE oids AS ( "
-        /* the pg_catalog.unknown type itself */
-                                "    SELECT 'pg_catalog.unknown'::pg_catalog.regtype AS oid "
-                                "    UNION ALL "
-                                "    SELECT * FROM ( "
-        /* domains on the type */
-                                "        WITH x AS (SELECT oid FROM oids) "
-                                "            SELECT t.oid FROM pg_catalog.pg_type t, x WHERE typbasetype = x.oid AND
typtype= 'd' " 
-                                "            UNION "
-        /* composite types containing the type */
-                                "            SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_class c,
pg_catalog.pg_attributea, x " 
-                                "            WHERE t.typtype = 'c' AND "
-                                "                  t.oid = c.reltype AND "
-                                "                  c.oid = a.attrelid AND "
-                                "                  NOT a.attisdropped AND "
-                                "                  a.atttypid = x.oid "
-                                "    ) foo "
-                                ") "
-                                "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 (SELECT oid FROM oids) AND "
-                                "        c.relkind IN ("
-                                CppAsString2(RELKIND_RELATION) ", "
-                                CppAsString2(RELKIND_MATVIEW) ") AND "
-                                "        c.relnamespace = n.oid AND "
-        /* exclude possible orphaned temp tables */
-                                "        n.nspname !~ '^pg_temp_' AND "
-                                "        n.nspname !~ '^pg_toast_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,
-                         strerror(errno));
-            if (!db_used)
-            {
-                fprintf(script, "In 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)
+    if (check_for_data_type_usage(cluster, "pg_catalog.unknown", output_path))
     {
         pg_log(PG_REPORT, "fatal\n");
         pg_fatal("Your installation contains the \"unknown\" data type in user tables.  This\n"
@@ -456,105 +395,18 @@ old_9_6_invalidate_hash_indexes(ClusterInfo *cluster, bool check_mode)
  *    which does affect the storage (name is by-ref, but not varlena). This
  *    means user tables using sql_identifier for columns are broken because
  *    the on-disk format is different.
- *
- *    We need to check all objects that might store sql_identifier on disk,
- *    i.e. tables, matviews and indexes. Also check composite types in case
- *    they are used in this context.
  */
 void
 old_11_check_for_sql_identifier_data_type_usage(ClusterInfo *cluster)
 {
-    int            dbnum;
-    FILE       *script = NULL;
-    bool        found = false;
     char        output_path[MAXPGPATH];

     prep_status("Checking for invalid \"sql_identifier\" user columns");

     snprintf(output_path, sizeof(output_path), "tables_using_sql_identifier.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);
-
-        /*
-         * We need the recursive CTE because the sql_identifier may be wrapped
-         * either in a domain or composite type, or both (in arbitrary order).
-         */
-        res = executeQueryOrDie(conn,
-                                "WITH RECURSIVE oids AS ( "
-        /* the sql_identifier type itself */
-                                "    SELECT 'information_schema.sql_identifier'::pg_catalog.regtype AS oid "
-                                "    UNION ALL "
-                                "    SELECT * FROM ( "
-        /* domains on the type */
-                                "        WITH x AS (SELECT oid FROM oids) "
-                                "            SELECT t.oid FROM pg_catalog.pg_type t, x WHERE typbasetype = x.oid AND
typtype= 'd' " 
-                                "            UNION "
-        /* composite types containing the type */
-                                "            SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_class c,
pg_catalog.pg_attributea, x " 
-                                "            WHERE t.typtype = 'c' AND "
-                                "                  t.oid = c.reltype AND "
-                                "                  c.oid = a.attrelid AND "
-                                "                  NOT a.attisdropped AND "
-                                "                  a.atttypid = x.oid "
-                                "    ) foo "
-                                ") "
-                                "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 (SELECT oid FROM oids) AND "
-                                "        c.relkind IN ("
-                                CppAsString2(RELKIND_RELATION) ", "
-                                CppAsString2(RELKIND_MATVIEW) ", "
-                                CppAsString2(RELKIND_INDEX) ") AND "
-                                "        c.relnamespace = n.oid AND "
-        /* exclude possible orphaned temp tables */
-                                "        n.nspname !~ '^pg_temp_' AND "
-                                "        n.nspname !~ '^pg_toast_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,
-                         strerror(errno));
-            if (!db_used)
-            {
-                fprintf(script, "In 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)
+    if (check_for_data_type_usage(cluster, "information_schema.sql_identifier",
+                                  output_path))
     {
         pg_log(PG_REPORT, "fatal\n");
         pg_fatal("Your installation contains the \"sql_identifier\" data type in user tables\n"
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index 0dff96b..a4d9f7b 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -144,13 +144,27 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
         /* domains on any type selected so far */
                           "            SELECT t.oid FROM pg_catalog.pg_type t, x WHERE typbasetype = x.oid AND typtype
='d' " 
                           "            UNION ALL "
+        /* arrays over any type selected so far */
+                          "            SELECT t.oid FROM pg_catalog.pg_type t, x WHERE typelem = x.oid AND typtype =
'b'" 
+                          "            UNION ALL "
         /* composite types containing any type selected so far */
                           "            SELECT t.oid FROM pg_catalog.pg_type t, pg_catalog.pg_class c,
pg_catalog.pg_attributea, x " 
                           "            WHERE t.typtype = 'c' AND "
                           "                  t.oid = c.reltype AND "
                           "                  c.oid = a.attrelid AND "
                           "                  NOT a.attisdropped AND "
-                          "                  a.atttypid = x.oid "
+                          "                  a.atttypid = x.oid ",
+                          typename);
+
+        /* Ranges came in in 9.2 */
+        if (GET_MAJOR_VERSION(cluster->major_version) >= 902)
+            appendPQExpBuffer(&querybuf,
+                              "            UNION ALL "
+            /* ranges containing any type selected so far */
+                              "            SELECT t.oid FROM pg_catalog.pg_type t, pg_range r, x "
+                              "            WHERE t.typtype = 'r' AND r.rngtypid = t.oid AND r.rngsubtype = x.oid");
+
+        appendPQExpBuffer(&querybuf,
                           "    ) foo "
                           ") "
         /* now look for stored columns of any such type */
@@ -169,8 +183,7 @@ check_for_data_type_usage(ClusterInfo *cluster, const char *typename,
         /* exclude possible orphaned temp tables */
                           "        n.nspname !~ '^pg_temp_' AND "
                           "        n.nspname !~ '^pg_toast_temp_' AND "
-                          "        n.nspname NOT IN ('pg_catalog', 'information_schema')",
-                          typename);
+                          "        n.nspname NOT IN ('pg_catalog', 'information_schema')");

         res = executeQueryOrDie(conn, "%s", querybuf.data);


pgsql-hackers by date:

Previous
From: Magnus Hagander
Date:
Subject: Re: ssl passphrase callback
Next
From: Daniel Gustafsson
Date:
Subject: Re: pg_upgrade fails to detect unsupported arrays and ranges