Re: Inconsistent error message wording for REINDEX CONCURRENTLY

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Inconsistent error message wording for REINDEX CONCURRENTLY
Дата
Msg-id 2321.1557263978@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: Inconsistent error message wording for REINDEX CONCURRENTLY  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: Inconsistent error message wording for REINDEX CONCURRENTLY  (Michael Paquier <michael@paquier.xyz>)
Список pgsql-hackers
I wrote:
> After looking around a bit, I propose that we invent
> "IsCatalogRelationOid(Oid reloid)" (not wedded to that name), which
> is a wrapper around IsCatalogClass() that does the needful syscache
> lookup for you.  Aside from this use-case, it could be used in
> sepgsql/dml.c, which I see is also using
> IsSystemNamespace(get_rel_namespace(oid)) for the wrong thing.

> I'm also thinking that it'd be a good idea to rename IsSystemNamespace
> to IsCatalogNamespace.  The existing naming is totally confusing given
> that it doesn't square with the distinction between IsSystemRelation
> and IsCatalogRelation (ie that the former includes user toast tables).
> There are only five external callers of it, and per this discussion
> at least two of them are wrong anyway.

After studying the callers of these catalog.c functions for awhile,
I realized that IsCatalogRelation/Class are really fundamentally wrong,
and have been so for a long time.  The reason is that while they will
return FALSE for tables in the information_schema, they will return
TRUE for toast tables attached to the information_schema tables.
(They're toast tables, and they have OIDs below FirstNormalObjectId,
so there you have it.)  This is wrong on its face: if those tables don't
need to be protected as catalogs, why should their TOAST appendages
need it?  Moreover, if you drop and recreate information_schema, you'll
start getting different behavior for them, which is even sillier.

I was driven to this realization by the following very confused (and
confusing) bit in ReindexMultipleTables:

        /*
         * Skip system tables that index_create() would reject to index
         * concurrently.  XXX We need the additional check for
         * FirstNormalObjectId to skip information_schema tables, because
         * IsCatalogClass() here does not cover information_schema, but the
         * check in index_create() will error on the TOAST tables of
         * information_schema tables.
         */
        if (concurrent &&
            (IsCatalogClass(relid, classtuple) || relid < FirstNormalObjectId))
        {

That's nothing but a hack, and the reason it's necessary is that
index_create will throw error if IsCatalogRelation is true, which
it will be for information_schema TOAST tables --- but not for their
parent tables that are being examined here.

After looking around, it seems to me that the correct definition for
IsCatalogRelation is just "is the OID less than FirstBootstrapObjectId?".
Currently we could actually restrict it to "less than
FirstGenbkiObjectId", because all the catalogs, indexes, and TOAST tables
have hand-assigned OIDs --- but perhaps someday we'll let genbki.pl
assign some of those OIDs, so I prefer the weaker constraint.  In any
case, this gives us a correct separation between objects that are
traceable to the bootstrap data and those that are created by plain SQL
later in initdb.

With this, the Form_pg_class argument to IsCatalogClass becomes
vestigial.  I'm tempted to get rid of that function altogether in
favor of direct calls to IsCatalogRelationOid, but haven't done so
in the attached.

Comments?

            regards, tom lane

diff --git a/contrib/sepgsql/dml.c b/contrib/sepgsql/dml.c
index cc934b5..2892346 100644
--- a/contrib/sepgsql/dml.c
+++ b/contrib/sepgsql/dml.c
@@ -161,12 +161,10 @@ check_relation_privileges(Oid relOid,
      */
     if (sepgsql_getenforce() > 0)
     {
-        Oid            relnamespace = get_rel_namespace(relOid);
-
-        if (IsSystemNamespace(relnamespace) &&
-            (required & (SEPG_DB_TABLE__UPDATE |
+        if ((required & (SEPG_DB_TABLE__UPDATE |
                          SEPG_DB_TABLE__INSERT |
-                         SEPG_DB_TABLE__DELETE)) != 0)
+                         SEPG_DB_TABLE__DELETE)) != 0 &&
+            IsCatalogRelationOid(relOid))
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("SELinux: hardwired security policy violation")));
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 6d8c746..b5e554f 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -53,15 +53,18 @@

 /*
  * IsSystemRelation
- *        True iff the relation is either a system catalog or toast table.
- *        By a system catalog, we mean one that created in the pg_catalog schema
- *        during initdb.  User-created relations in pg_catalog don't count as
- *        system catalogs.
+ *        True iff the relation is either a system catalog or a toast table.
+ *        See IsCatalogRelation for the exact definition of a system catalog.
  *
- *        NB: TOAST relations are considered system relations by this test
- *        for compatibility with the old IsSystemRelationName function.
- *        This is appropriate in many places but not all.  Where it's not,
- *        also check IsToastRelation or use IsCatalogRelation().
+ *        We treat toast tables of user relations as "system relations" for
+ *        protection purposes, e.g. you can't change their schemas without
+ *        special permissions.  Therefore, most uses of this function are
+ *        checking whether allow_system_table_mods restrictions apply.
+ *        For other purposes, consider whether you shouldn't be using
+ *        IsCatalogRelation instead.
+ *
+ *        This function does not perform any catalog accesses.
+ *        Some callers rely on that!
  */
 bool
 IsSystemRelation(Relation relation)
@@ -78,67 +81,86 @@ IsSystemRelation(Relation relation)
 bool
 IsSystemClass(Oid relid, Form_pg_class reltuple)
 {
-    return IsToastClass(reltuple) || IsCatalogClass(relid, reltuple);
+    /* IsCatalogRelationOid is a bit faster, so test that first */
+    return (IsCatalogRelationOid(relid) || IsToastClass(reltuple));
 }

 /*
  * IsCatalogRelation
- *        True iff the relation is a system catalog, or the toast table for
- *        a system catalog.  By a system catalog, we mean one that created
- *        in the pg_catalog schema during initdb.  As with IsSystemRelation(),
- *        user-created relations in pg_catalog don't count as system catalogs.
+ *        True iff the relation is a system catalog.
+ *
+ *        By a system catalog, we mean one that is created during the bootstrap
+ *        phase of initdb.  That includes not just the catalogs per se, but
+ *        also their indexes, and TOAST tables and indexes if any.
  *
- *        Note that IsSystemRelation() returns true for ALL toast relations,
- *        but this function returns true only for toast relations of system
- *        catalogs.
+ *        This function does not perform any catalog accesses.
+ *        Some callers rely on that!
  */
 bool
 IsCatalogRelation(Relation relation)
 {
-    return IsCatalogClass(RelationGetRelid(relation), relation->rd_rel);
+    return IsCatalogRelationOid(RelationGetRelid(relation));
 }

 /*
  * IsCatalogClass
- *        True iff the relation is a system catalog relation.
- *
- * Check IsCatalogRelation() for details.
+ *        Like the above, but takes a Form_pg_class as argument.
+ *        Used when we do not want to open the relation and have to
+ *        search pg_class directly.
  */
 bool
 IsCatalogClass(Oid relid, Form_pg_class reltuple)
 {
-    Oid            relnamespace = reltuple->relnamespace;
+    return IsCatalogRelationOid(relid);
+}

+/*
+ * IsCatalogRelationOid
+ *        True iff the relation identified by this OID is a system catalog.
+ *
+ *        By a system catalog, we mean one that is created during the bootstrap
+ *        phase of initdb.  That includes not just the catalogs per se, but
+ *        also their indexes, and TOAST tables and indexes if any.
+ *
+ *        This function does not perform any catalog accesses.
+ *        Some callers rely on that!
+ */
+bool
+IsCatalogRelationOid(Oid relid)
+{
     /*
-     * Never consider relations outside pg_catalog/pg_toast to be catalog
-     * relations.
-     */
-    if (!IsSystemNamespace(relnamespace) && !IsToastNamespace(relnamespace))
-        return false;
-
-    /* ----
-     * Check whether the oid was assigned during initdb, when creating the
-     * initial template database. Minus the relations in information_schema
-     * excluded above, these are integral part of the system.
-     * We could instead check whether the relation is pinned in pg_depend, but
-     * this is noticeably cheaper and doesn't require catalog access.
+     * We consider a relation to be a system catalog if it has an OID that was
+     * manually assigned or assigned by genbki.pl.  This includes all the
+     * defined catalogs, their indexes, and their TOAST tables and indexes.
+     *
+     * This rule excludes the relations in information_schema, which are not
+     * integral to the system and can be treated the same as user relations.
+     * (Since it's valid to drop and recreate information_schema, any rule
+     * that did not act this way would be wrong.)
      *
-     * This test is safe since even an oid wraparound will preserve this
-     * property (cf. GetNewObjectId()) and it has the advantage that it works
-     * correctly even if a user decides to create a relation in the pg_catalog
-     * namespace.
-     * ----
+     * This test is reliable since an OID wraparound will skip this range of
+     * OIDs; see GetNewObjectId().
      */
-    return relid < FirstNormalObjectId;
+    return (relid < (Oid) FirstBootstrapObjectId);
 }

 /*
  * IsToastRelation
  *        True iff relation is a TOAST support relation (or index).
+ *
+ *        Does not perform any catalog accesses.
  */
 bool
 IsToastRelation(Relation relation)
 {
+    /*
+     * What we actually check is whether the relation belongs to a pg_toast
+     * namespace.  This should be equivalent because of restrictions that are
+     * enforced elsewhere against creating user relations in, or moving
+     * relations into/out of, a pg_toast namespace.  Notice also that this
+     * will not say "true" for toast tables belonging to other sessions' temp
+     * tables; we expect that other mechanisms will prevent access to those.
+     */
     return IsToastNamespace(RelationGetNamespace(relation));
 }

@@ -157,14 +179,16 @@ IsToastClass(Form_pg_class reltuple)
 }

 /*
- * IsSystemNamespace
+ * IsCatalogNamespace
  *        True iff namespace is pg_catalog.
  *
+ *        Does not perform any catalog accesses.
+ *
  * NOTE: the reason this isn't a macro is to avoid having to include
  * catalog/pg_namespace.h in a lot of places.
  */
 bool
-IsSystemNamespace(Oid namespaceId)
+IsCatalogNamespace(Oid namespaceId)
 {
     return namespaceId == PG_CATALOG_NAMESPACE;
 }
@@ -173,9 +197,13 @@ IsSystemNamespace(Oid namespaceId)
  * IsToastNamespace
  *        True iff namespace is pg_toast or my temporary-toast-table namespace.
  *
+ *        Does not perform any catalog accesses.
+ *
  * Note: this will return false for temporary-toast-table namespaces belonging
  * to other backends.  Those are treated the same as other backends' regular
  * temp table namespaces, and access is prevented where appropriate.
+ * If you need to check for those, you may be able to use isAnyTempNamespace,
+ * but beware that that does involve a catalog access.
  */
 bool
 IsToastNamespace(Oid namespaceId)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ee6b72e..6cffe55 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -324,7 +324,7 @@ heap_create(const char *relname,
      * user defined relation, not a system one.
      */
     if (!allow_system_table_mods &&
-        ((IsSystemNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
+        ((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) ||
          IsToastNamespace(relnamespace)) &&
         IsNormalProcessingMode())
         ereport(ERROR,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 663407c..ccbe08c 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2569,15 +2569,11 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
             continue;

         /*
-         * Skip system tables that index_create() would reject to index
-         * concurrently.  XXX We need the additional check for
-         * FirstNormalObjectId to skip information_schema tables, because
-         * IsCatalogClass() here does not cover information_schema, but the
-         * check in index_create() will error on the TOAST tables of
-         * information_schema tables.
+         * Skip system tables, since index_create() would reject indexing them
+         * concurrently (and it would likely fail if we tried).
          */
         if (concurrent &&
-            (IsCatalogClass(relid, classtuple) || relid < FirstNormalObjectId))
+            IsCatalogClass(relid, classtuple))
         {
             if (!concurrent_warning)
                 ereport(WARNING,
@@ -2821,7 +2817,7 @@ ReindexRelationConcurrently(Oid relationOid, int options)
                              errmsg("concurrent reindex is not supported for shared relations")));

                 /* A system catalog cannot be reindexed concurrently */
-                if (IsSystemNamespace(get_rel_namespace(heapId)))
+                if (IsCatalogRelationOid(heapId))
                     ereport(ERROR,
                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                              errmsg("concurrent reindex is not supported for catalog relations")));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8e4743d..97c58c8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -12453,9 +12453,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
          * Also, explicitly avoid any shared tables, temp tables, or TOAST
          * (TOAST will be moved with the main table).
          */
-        if (IsSystemNamespace(relForm->relnamespace) || relForm->relisshared ||
+        if (IsCatalogNamespace(relForm->relnamespace) ||
+            relForm->relisshared ||
             isAnyTempNamespace(relForm->relnamespace) ||
-            relForm->relnamespace == PG_TOAST_NAMESPACE)
+            IsToastNamespace(relForm->relnamespace))
             continue;

         /* Only move the object type requested */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index eaf2b18..d0f6f71 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -3316,8 +3316,8 @@ RelationBuildLocalRelation(const char *relname,
     else
         rel->rd_rel->relispopulated = true;

-    /* system relations and non-table objects don't have one */
-    if (!IsSystemNamespace(relnamespace) &&
+    /* set replica identity -- system catalogs and non-tables don't have one */
+    if (!IsCatalogNamespace(relnamespace) &&
         (relkind == RELKIND_RELATION ||
          relkind == RELKIND_MATVIEW ||
          relkind == RELKIND_PARTITIONED_TABLE))
diff --git a/src/include/catalog/catalog.h b/src/include/catalog/catalog.h
index a58d09d..47c48b8 100644
--- a/src/include/catalog/catalog.h
+++ b/src/include/catalog/catalog.h
@@ -26,7 +26,9 @@ extern bool IsSystemClass(Oid relid, Form_pg_class reltuple);
 extern bool IsToastClass(Form_pg_class reltuple);
 extern bool IsCatalogClass(Oid relid, Form_pg_class reltuple);

-extern bool IsSystemNamespace(Oid namespaceId);
+extern bool IsCatalogRelationOid(Oid relid);
+
+extern bool IsCatalogNamespace(Oid namespaceId);
 extern bool IsToastNamespace(Oid namespaceId);

 extern bool IsReservedName(const char *name);

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Stephen Frost
Дата:
Сообщение: Re: make \d pg_toast.foo show its indices
Следующее
От: David Fetter
Дата:
Сообщение: Re: New EXPLAIN option: ALL