Be more careful about marking catalog columns NOT NULL by default.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 21 Jul 2020 17:03:48 +0000 (13:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 21 Jul 2020 17:03:48 +0000 (13:03 -0400)
The bug fixed in commit 72eab84a5 would not have occurred if initdb
had a less surprising rule about which columns should be marked
NOT NULL by default.  Let's make that rule be strictly that the
column must be fixed-width and its predecessors must be fixed-width
and NOT NULL, removing the hacky and unsafe exceptions for oidvector
and int2vector.

Since we do still want all existing oidvector and int2vector columns
to be marked NOT NULL, we have to put BKI_FORCE_NOT_NULL labels on
them.  But making this less magic and more documented seems like a
good idea, even if it's a shade more verbose.

I didn't bump catversion since the initial catalog contents are
not actually changed by this patch.  Note however that the
contents of postgres.bki do change, and feeding an old copy of
that to a new backend will produce wrong results.

Discussion: https://postgr.es/m/204760.1595181800@sss.pgh.pa.us

doc/src/sgml/bki.sgml
src/backend/bootstrap/bootstrap.c
src/backend/catalog/genbki.pl
src/include/catalog/genbki.h
src/include/catalog/pg_index.h
src/include/catalog/pg_partitioned_table.h
src/include/catalog/pg_proc.h
src/include/catalog/pg_statistic_ext.h
src/include/catalog/pg_trigger.h

index 4e7568f5ce9283d0d470e64a4ac176e3bb7c56e2..272a10a98c5ac316d8ee40b7c547fe00ac680f19 100644 (file)
    require all columns that should be non-nullable to be marked so
    in <structname>pg_attribute</structname>.  The bootstrap code will
    automatically mark catalog columns as <literal>NOT NULL</literal>
-   if they are fixed-width and are not preceded by any nullable column.
+   if they are fixed-width and are not preceded by any nullable or
+   variable-width column.
    Where this rule is inadequate, you can force correct marking by using
    <literal>BKI_FORCE_NOT_NULL</literal>
    and <literal>BKI_FORCE_NULL</literal> annotations as needed.
index 5480a024e05b5247c18792715e1baf3ba88c2ac3..45b7efbe4659f8cb5b824f9b414e75e614be8b50 100644 (file)
@@ -770,25 +770,18 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 
                /*
                 * Mark as "not null" if type is fixed-width and prior columns are
-                * too.  This corresponds to case where column can be accessed
-                * directly via C struct declaration.
-                *
-                * oidvector and int2vector are also treated as not-nullable, even
-                * though they are no longer fixed-width.
+                * likewise fixed-width and not-null.  This corresponds to case where
+                * column can be accessed directly via C struct declaration.
                 */
-#define MARKNOTNULL(att) \
-               ((att)->attlen > 0 || \
-                (att)->atttypid == OIDVECTOROID || \
-                (att)->atttypid == INT2VECTOROID)
-
-               if (MARKNOTNULL(attrtypes[attnum]))
+               if (attrtypes[attnum]->attlen > 0)
                {
                        int                     i;
 
                        /* check earlier attributes */
                        for (i = 0; i < attnum; i++)
                        {
-                               if (!attrtypes[i]->attnotnull)
+                               if (attrtypes[i]->attlen <= 0 ||
+                                       !attrtypes[i]->attnotnull)
                                        break;
                        }
                        if (i == attnum)
index b07537fbbac9909c458d1838344531d0474b3607..dc5f442397a4cb51196d7100d151084c17b61683 100644 (file)
@@ -713,8 +713,8 @@ sub gen_pg_attribute
                push @tables_needing_macros, $table_name;
 
                # Generate entries for user attributes.
-               my $attnum       = 0;
-               my $priornotnull = 1;
+               my $attnum          = 0;
+               my $priorfixedwidth = 1;
                foreach my $attr (@{ $table->{columns} })
                {
                        $attnum++;
@@ -722,8 +722,12 @@ sub gen_pg_attribute
                        $row{attnum}   = $attnum;
                        $row{attrelid} = $table->{relation_oid};
 
-                       morph_row_for_pgattr(\%row, $schema, $attr, $priornotnull);
-                       $priornotnull &= ($row{attnotnull} eq 't');
+                       morph_row_for_pgattr(\%row, $schema, $attr, $priorfixedwidth);
+
+                       # Update $priorfixedwidth --- must match morph_row_for_pgattr
+                       $priorfixedwidth &=
+                         ($row{attnotnull} eq 't'
+                                 && ($row{attlen} eq 'NAMEDATALEN' || $row{attlen} > 0));
 
                        # If it's bootstrapped, put an entry in postgres.bki.
                        print_bki_insert(\%row, $schema) if $table->{bootstrap};
@@ -765,13 +769,13 @@ sub gen_pg_attribute
 
 # Given $pgattr_schema (the pg_attribute schema for a catalog sufficient for
 # AddDefaultValues), $attr (the description of a catalog row), and
-# $priornotnull (whether all prior attributes in this catalog are not null),
+# $priorfixedwidth (all prior columns are fixed-width and not null),
 # modify the $row hashref for print_bki_insert.  This includes setting data
 # from the corresponding pg_type element and filling in any default values.
 # Any value not handled here must be supplied by caller.
 sub morph_row_for_pgattr
 {
-       my ($row, $pgattr_schema, $attr, $priornotnull) = @_;
+       my ($row, $pgattr_schema, $attr, $priorfixedwidth) = @_;
        my $attname = $attr->{name};
        my $atttype = $attr->{type};
 
@@ -801,19 +805,18 @@ sub morph_row_for_pgattr
        {
                $row->{attnotnull} = 'f';
        }
-       elsif ($priornotnull)
+       elsif ($priorfixedwidth)
        {
 
                # attnotnull will automatically be set if the type is
-               # fixed-width and prior columns are all NOT NULL ---
-               # compare DefineAttr in bootstrap.c. oidvector and
-               # int2vector are also treated as not-nullable.
+               # fixed-width and prior columns are likewise fixed-width
+               # and NOT NULL --- compare DefineAttr in bootstrap.c.
+               # At this point the width of type name is still symbolic,
+               # so we need a special test.
                $row->{attnotnull} =
-                   $type->{typname} eq 'oidvector'  ? 't'
-                 : $type->{typname} eq 'int2vector' ? 't'
-                 : $type->{typlen} eq 'NAMEDATALEN' ? 't'
-                 : $type->{typlen} > 0              ? 't'
-                 :                                    'f';
+                   $row->{attlen} eq 'NAMEDATALEN' ? 't'
+                 : $row->{attlen} > 0              ? 't'
+                 :                                   'f';
        }
        else
        {
index 4a6c8636daf010708344c37c6efbe5f929e526f0..8cac7ec878940feb80e0a567af96ccab78625b09 100644 (file)
@@ -46,8 +46,8 @@
 /*
  * Variable-length catalog fields (except possibly the first not nullable one)
  * should not be visible in C structures, so they are made invisible by #ifdefs
- * of an undefined symbol.  See also MARKNOTNULL in bootstrap.c for how this is
- * handled.
+ * of an undefined symbol.  See also the BOOTCOL_NULL_AUTO code in bootstrap.c
+ * for how this is handled.
  */
 #undef CATALOG_VARLEN
 
index d3d7ea77fbbe59eb143e53d1dc7d4c76aae33184..4a642f336fa60aab72d0089aef454b420c16b9e3 100644 (file)
@@ -44,12 +44,14 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO
        bool            indisreplident; /* is this index the identity for replication? */
 
        /* variable-length fields start here, but we allow direct access to indkey */
-       int2vector      indkey;                 /* column numbers of indexed cols, or 0 */
+       int2vector      indkey BKI_FORCE_NOT_NULL;      /* column numbers of indexed cols,
+                                                                                        * or 0 */
 
 #ifdef CATALOG_VARLEN
-       oidvector       indcollation;   /* collation identifiers */
-       oidvector       indclass;               /* opclass identifiers */
-       int2vector      indoption;              /* per-column flags (AM-specific meanings) */
+       oidvector       indcollation BKI_FORCE_NOT_NULL;        /* collation identifiers */
+       oidvector       indclass BKI_FORCE_NOT_NULL;    /* opclass identifiers */
+       int2vector      indoption BKI_FORCE_NOT_NULL;   /* per-column flags
+                                                                                                * (AM-specific meanings) */
        pg_node_tree indexprs;          /* expression trees for index attributes that
                                                                 * are not simple column references; one for
                                                                 * each zero entry in indkey[] */
index a73cd0d3a4456c9140afba11291fc5fd02f550b9..7ee0419373ca6df35cb6961b32bfb0546917113f 100644 (file)
@@ -41,13 +41,17 @@ CATALOG(pg_partitioned_table,3350,PartitionedRelationId)
         * field of a heap tuple can be reliably accessed using its C struct
         * offset, as previous fields are all non-nullable fixed-length fields.
         */
-       int2vector      partattrs;              /* each member of the array is the attribute
-                                                                * number of a partition key column, or 0 if
-                                                                * the column is actually an expression */
+       int2vector      partattrs BKI_FORCE_NOT_NULL;   /* each member of the array is
+                                                                                                * the attribute number of a
+                                                                                                * partition key column, or 0
+                                                                                                * if the column is actually
+                                                                                                * an expression */
 
 #ifdef CATALOG_VARLEN
-       oidvector       partclass;              /* operator class to compare keys */
-       oidvector       partcollation;  /* user-specified collation for keys */
+       oidvector       partclass BKI_FORCE_NOT_NULL;   /* operator class to compare
+                                                                                                * keys */
+       oidvector       partcollation BKI_FORCE_NOT_NULL;       /* user-specified
+                                                                                                        * collation for keys */
        pg_node_tree partexprs;         /* list of expressions in the partition key;
                                                                 * one item for each zero entry in partattrs[] */
 #endif
index 65e8c9f0546db4d8818b3324a470d40a4f18f1b4..b50fa25dbd8600fe0bc17862e46d78b357f74792 100644 (file)
@@ -92,7 +92,7 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
         */
 
        /* parameter types (excludes OUT params) */
-       oidvector       proargtypes BKI_LOOKUP(pg_type);
+       oidvector       proargtypes BKI_LOOKUP(pg_type) BKI_FORCE_NOT_NULL;
 
 #ifdef CATALOG_VARLEN
 
index a8cb16997a7c26c18b6e26a9ce3b438ad3ce5b2f..8747903fc735acbc29f99ae61d180a9d12fad2c3 100644 (file)
@@ -47,7 +47,7 @@ CATALOG(pg_statistic_ext,3381,StatisticExtRelationId)
         * variable-length fields start here, but we allow direct access to
         * stxkeys
         */
-       int2vector      stxkeys;                /* array of column keys */
+       int2vector      stxkeys BKI_FORCE_NOT_NULL; /* array of column keys */
 
 #ifdef CATALOG_VARLEN
        char            stxkind[1] BKI_FORCE_NOT_NULL;  /* statistics kinds requested
index 9612b9bdd65b0996e95617d5de47b819dc1d5445..fa5761b784599cc2cb9670a2f5b825afd3b4ff92 100644 (file)
@@ -54,7 +54,8 @@ CATALOG(pg_trigger,2620,TriggerRelationId)
         * Variable-length fields start here, but we allow direct access to
         * tgattr. Note: tgattr and tgargs must not be null.
         */
-       int2vector      tgattr;                 /* column numbers, if trigger is on columns */
+       int2vector      tgattr BKI_FORCE_NOT_NULL;      /* column numbers, if trigger is
+                                                                                        * on columns */
 
 #ifdef CATALOG_VARLEN
        bytea           tgargs BKI_FORCE_NOT_NULL;      /* first\000second\000tgnargs\000 */