Don't set a fast default for anything but a plain table
authorAndrew Dunstan
Fri, 18 Jun 2021 11:44:58 +0000 (07:44 -0400)
committerAndrew Dunstan
Fri, 18 Jun 2021 11:44:58 +0000 (07:44 -0400)
The fast default code added in Release 11 omitted to check that the
table a fast default was being added to was a plain table. Thus one
could be added to a foreign table, which predicably blows up. Here we
perform that check.

In addition, on the back branches, since some of these might have
escaped into the wild, if we encounter a missing value for
an attribute of something other than a plain table we ignore it.

Fixes bug #17056

Backpatch to release 11,

Reviewed by: Andres Freund, Álvaro Herrera and Tom Lane

src/backend/catalog/heap.c
src/backend/commands/tablecmds.c
src/backend/utils/cache/relcache.c
src/test/regress/expected/fast_default.out
src/test/regress/sql/fast_default.sql

index b34b8b94b3a2d1410dc75960260e9e4852403351..a42070fd9250b3e2d382d2e8e37ea255314cf2c1 100644 (file)
@@ -2120,6 +2120,13 @@ SetAttrMissing(Oid relid, char *attname, char *value)
    /* lock the table the attribute belongs to */
    tablerel = table_open(relid, AccessExclusiveLock);
 
+   /* Don't do anything unless it's a plain table */
+   if (tablerel->rd_rel->relkind != RELKIND_RELATION)
+   {
+       table_close(tablerel, AccessExclusiveLock);
+       return;
+   }
+
    /* Lock the attribute row and get the data */
    attrrel = table_open(AttributeRelationId, RowExclusiveLock);
    atttup = SearchSysCacheAttName(relid, attname);
@@ -2246,7 +2253,8 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
        valuesAtt[Anum_pg_attribute_atthasdef - 1] = true;
        replacesAtt[Anum_pg_attribute_atthasdef - 1] = true;
 
-       if (add_column_mode && !attgenerated)
+       if (rel->rd_rel->relkind == RELKIND_RELATION  && add_column_mode &&
+           !attgenerated)
        {
            expr2 = expression_planner(expr2);
            estate = CreateExecutorState();
index 70337c7c07205b5fc58506eacca7bbcd8853b01f..135fa4698172241b53d4b254b95f1fa91a894386 100644 (file)
@@ -11774,9 +11774,10 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
    /*
     * Here we go --- change the recorded column type and collation.  (Note
     * heapTup is a copy of the syscache entry, so okay to scribble on.) First
-    * fix up the missing value if any.
+    * fix up the missing value if any. There shouldn't be any missing values
+    * for anything except plain tables, but if there are, ignore them.
     */
-   if (attTup->atthasmissing)
+   if (rel->rd_rel->relkind == RELKIND_RELATION  && attTup->atthasmissing)
    {
        Datum       missingval;
        bool        missingNull;
index b53d92b4daed623aacf123dea26f3206047ccf9c..23405711d1de5f589d9a4224848ed7f9c9c17e1d 100644 (file)
@@ -550,6 +550,7 @@ RelationBuildTupleDesc(Relation relation)
    {
        Form_pg_attribute attp;
        int         attnum;
+       bool        atthasmissing;
 
        attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple);
 
@@ -563,6 +564,22 @@ RelationBuildTupleDesc(Relation relation)
               attp,
               ATTRIBUTE_FIXED_PART_SIZE);
 
+       /*
+        * Fix atthasmissing flag - it's only for plain tables. Others
+        * should not have missing values set, but there may be some left from
+        * before when we placed that check, so this code defensively ignores
+        * such values.
+        */
+       atthasmissing = attp->atthasmissing;
+       if (relation->rd_rel->relkind != RELKIND_RELATION && atthasmissing)
+       {
+           Form_pg_attribute nattp;
+
+           atthasmissing = false;
+           nattp = TupleDescAttr(relation->rd_att, attnum - 1);
+           nattp->atthasmissing = false;
+       }
+
        /* Update constraint/default info */
        if (attp->attnotnull)
            constr->has_not_null = true;
@@ -584,7 +601,7 @@ RelationBuildTupleDesc(Relation relation)
        }
 
        /* Likewise for a missing value */
-       if (attp->atthasmissing)
+       if (atthasmissing)
        {
            Datum       missingval;
            bool        missingNull;
index 10bc5ff757c211c4fa67f4bede754d5cb744c19a..91f25717b5a5b064ec9ceb01c6743100cf38930c 100644 (file)
@@ -797,7 +797,26 @@ SELECT * FROM t WHERE a IS NULL;
 (1 row)
 
 ROLLBACK;
+-- verify that a default set on a non-plain table doesn't set a missing
+-- value on the attribute
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+CREATE FOREIGN TABLE ft1 (c1 integer NOT NULL) SERVER s0;
+ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer DEFAULT 0;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+SELECT count(*)
+  FROM pg_attribute
+  WHERE attrelid = 'ft1'::regclass AND
+    (attmissingval IS NOT NULL OR atthasmissing);
+ count 
+-------
+     0
+(1 row)
+
 -- cleanup
+DROP FOREIGN TABLE ft1;
+DROP SERVER s0;
+DROP FOREIGN DATA WRAPPER dummy;
 DROP TABLE vtype;
 DROP TABLE vtype2;
 DROP TABLE follower;
index 4589b9e58d1708fb26acbd15e1d428776aaf97f4..16a3b7ca51d1a3e764638c1ea08ebcf9aeea2470 100644 (file)
@@ -524,8 +524,22 @@ SET LOCAL enable_seqscan = false;
 SELECT * FROM t WHERE a IS NULL;
 ROLLBACK;
 
+-- verify that a default set on a non-plain table doesn't set a missing
+-- value on the attribute
+CREATE FOREIGN DATA WRAPPER dummy;
+CREATE SERVER s0 FOREIGN DATA WRAPPER dummy;
+CREATE FOREIGN TABLE ft1 (c1 integer NOT NULL) SERVER s0;
+ALTER FOREIGN TABLE ft1 ADD COLUMN c8 integer DEFAULT 0;
+ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE char(10);
+SELECT count(*)
+  FROM pg_attribute
+  WHERE attrelid = 'ft1'::regclass AND
+    (attmissingval IS NOT NULL OR atthasmissing);
 
 -- cleanup
+DROP FOREIGN TABLE ft1;
+DROP SERVER s0;
+DROP FOREIGN DATA WRAPPER dummy;
 DROP TABLE vtype;
 DROP TABLE vtype2;
 DROP TABLE follower;