>From 9893065e8e5d311a00ec815c02646008056abc3b Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 20 Feb 2015 22:07:05 +0100
Subject: [PATCH 1/2] Allow forcing nullness of columns during bootstrap.

Bootstrap determines whether a column is null based on simple builtin
rules. Those work surprisingly well, but nonetheless a few existing
columns aren't set correctly. Additionally there's future patches where
forcing the nullness would be helpful.
---
 doc/src/sgml/bki.sgml               |  9 +++--
 src/backend/bootstrap/bootparse.y   | 13 ++++++--
 src/backend/bootstrap/bootscanner.l |  3 ++
 src/backend/bootstrap/bootstrap.c   | 56 ++++++++++++++++++++------------
 src/backend/catalog/Catalog.pm      | 24 ++++++++++++--
 src/backend/catalog/genbki.pl       | 65 +++++++++++++++++++++++++++----------
 src/backend/utils/Gen_fmgrtab.pl    |  2 +-
 src/include/bootstrap/bootstrap.h   |  6 +++-
 src/include/catalog/genbki.h        |  2 ++
 9 files changed, 131 insertions(+), 49 deletions(-)

diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml
index aaf500a..af6d8d1 100644
--- a/doc/src/sgml/bki.sgml
+++ b/doc/src/sgml/bki.sgml
@@ -75,9 +75,12 @@
      <optional><literal>without_oids</></optional>
      <optional><literal>rowtype_oid</> <replaceable>oid</></optional>
      (<replaceable class="parameter">name1</replaceable> =
-     <replaceable class="parameter">type1</replaceable> <optional>,
-     <replaceable class="parameter">name2</replaceable> = <replaceable
-     class="parameter">type2</replaceable>, ...</optional>)
+     <replaceable class="parameter">type1</replaceable>
+     <optional>FORCE NOT NULL | FORCE NULL </optional> <optional>,
+     <replaceable class="parameter">name2</replaceable> =
+     <replaceable class="parameter">type2</replaceable>
+     <optional>FORCE NOT NULL | FORCE NULL </optional>,
+     ...</optional>)
     </term>
 
     <listitem>
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 56fa1aa..9edd1a0 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -107,7 +107,7 @@ static int num_columns_read = 0;
 %type <list>  boot_index_params
 %type <ielem> boot_index_param
 %type <str>   boot_const boot_ident
-%type <ival>  optbootstrap optsharedrelation optwithoutoids
+%type <ival>  optbootstrap optsharedrelation optwithoutoids boot_column_nullness
 %type <oidval> oidspec optoideq optrowtypeoid
 
 %token <str> CONST_P ID
@@ -115,6 +115,7 @@ static int num_columns_read = 0;
 %token XDECLARE INDEX ON USING XBUILD INDICES UNIQUE XTOAST
 %token COMMA EQUALS LPAREN RPAREN
 %token OBJ_ID XBOOTSTRAP XSHARED_RELATION XWITHOUT_OIDS XROWTYPE_OID NULLVAL
+%token XFORCE XNOT XNULL
 
 %start TopLevel
 
@@ -427,14 +428,20 @@ boot_column_list:
 		;
 
 boot_column_def:
-		  boot_ident EQUALS boot_ident
+		  boot_ident EQUALS boot_ident boot_column_nullness
 				{
 				   if (++numattr > MAXATTR)
 						elog(FATAL, "too many columns");
-				   DefineAttr($1, $3, numattr-1);
+				   DefineAttr($1, $3, numattr-1, $4);
 				}
 		;
 
+boot_column_nullness:
+			XFORCE XNOT XNULL	{ $$ = BOOTCOL_NULL_FORCE_NOT_NULL; }
+		|	XFORCE XNULL		{  $$ = BOOTCOL_NULL_FORCE_NULL; }
+		| { $$ = BOOTCOL_NULL_AUTO; }
+		;
+
 oidspec:
 			boot_ident							{ $$ = atooid($1); }
 		;
diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l
index fa4e2ff..72714f4 100644
--- a/src/backend/bootstrap/bootscanner.l
+++ b/src/backend/bootstrap/bootscanner.l
@@ -109,6 +109,9 @@ insert			{ return(INSERT_TUPLE); }
 "on"			{ return(ON); }
 "using"			{ return(USING); }
 "toast"			{ return(XTOAST); }
+"FORCE"			{ return(XFORCE); }
+"NOT"			{ return(XNOT); }
+"NULL"			{ return(XNULL); }
 
 {arrayid}		{
 					yylval.str = MapArrayTypeName(yytext);
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index bc66eac..ad49964 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -642,7 +642,7 @@ closerel(char *name)
  * ----------------
  */
 void
-DefineAttr(char *name, char *type, int attnum)
+DefineAttr(char *name, char *type, int attnum, int nullness)
 {
 	Oid			typeoid;
 
@@ -697,30 +697,44 @@ DefineAttr(char *name, char *type, int attnum)
 	attrtypes[attnum]->atttypmod = -1;
 	attrtypes[attnum]->attislocal = true;
 
-	/*
-	 * 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.
-	 */
-#define MARKNOTNULL(att) \
-	((att)->attlen > 0 || \
-	 (att)->atttypid == OIDVECTOROID || \
-	 (att)->atttypid == INT2VECTOROID)
-
-	if (MARKNOTNULL(attrtypes[attnum]))
+	if (nullness == BOOTCOL_NULL_FORCE_NOT_NULL)
+	{
+		attrtypes[attnum]->attnotnull = true;
+	}
+	else if (nullness == BOOTCOL_NULL_FORCE_NULL)
 	{
-		int			i;
+		attrtypes[attnum]->attnotnull = false;
+	}
+	else
+	{
+		Assert(nullness == BOOTCOL_NULL_AUTO);
 
-		for (i = 0; i < attnum; i++)
+		/*
+		 * 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.
+		 */
+#define MARKNOTNULL(att) \
+		((att)->attlen > 0 || \
+		 (att)->atttypid == OIDVECTOROID || \
+		 (att)->atttypid == INT2VECTOROID)
+
+		if (MARKNOTNULL(attrtypes[attnum]))
 		{
-			if (!MARKNOTNULL(attrtypes[i]))
-				break;
+			int			i;
+
+			/* check earlier attributes */
+			for (i = 0; i < attnum; i++)
+			{
+				if (!attrtypes[i]->attnotnull)
+					break;
+			}
+			if (i == attnum)
+				attrtypes[attnum]->attnotnull = true;
 		}
-		if (i == attnum)
-			attrtypes[attnum]->attnotnull = true;
 	}
 }
 
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index c773eca..c7b1c17 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -161,7 +161,8 @@ sub Catalogs
 				}
 				else
 				{
-					my ($atttype, $attname) = split /\s+/, $_;
+					my %row;
+					my ($atttype, $attname, $attopt) = split /\s+/, $_;
 					die "parse error ($input_file)" unless $attname;
 					if (exists $RENAME_ATTTYPE{$atttype})
 					{
@@ -172,7 +173,26 @@ sub Catalogs
 						$attname = $1;
 						$atttype .= '[]';            # variable-length only
 					}
-					push @{ $catalog{columns} }, { $attname => $atttype };
+
+					$row{'type'} = $atttype;
+					$row{'name'} = $attname;
+
+					if (defined $attopt)
+					{
+						if ($attopt eq 'PG_FORCE_NULL')
+						{
+							$row{'forcenull'} = 1;
+						}
+						elsif ($attopt eq 'BKI_FORCE_NOT_NULL')
+						{
+							$row{'forcenotnull'} = 1;
+						}
+						else
+						{
+							die "unknown column option $attopt on column $attname"
+						}
+					}
+					push @{ $catalog{columns} }, \%row;
 				}
 			}
 		}
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index e1c7fe5..a5c78ee 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -118,17 +118,36 @@ foreach my $catname (@{ $catalogs->{names} })
 
 	my %bki_attr;
 	my @attnames;
+	my $first = 1;
+
+	print BKI " (\n";
 	foreach my $column (@{ $catalog->{columns} })
 	{
-		my ($attname, $atttype) = %$column;
-		$bki_attr{$attname} = $atttype;
+		my $attname = $column->{name};
+		my $atttype = $column->{type};
+		$bki_attr{$attname} = $column;
 		push @attnames, $attname;
+
+		if (!$first)
+		{
+			print BKI " ,\n";
+		}
+		$first = 0;
+
+		print BKI " $attname = $atttype";
+
+		if (defined $column->{forcenotnull})
+		{
+			print BKI " FORCE NOT NULL";
+		}
+		elsif (defined $column->{forcenull})
+		{
+			print BKI " FORCE NULL";
+		}
 	}
-	print BKI " (\n";
-	print BKI join " ,\n", map(" $_ = $bki_attr{$_}", @attnames);
 	print BKI "\n )\n";
 
-   # open it, unless bootstrap case (create bootstrap does this automatically)
+	# open it, unless bootstrap case (create bootstrap does this automatically)
 	if ($catalog->{bootstrap} eq '')
 	{
 		print BKI "open $catname\n";
@@ -210,7 +229,7 @@ foreach my $catname (@{ $catalogs->{names} })
 				# Store schemapg entries for later.
 				$row =
 				  emit_schemapg_row($row,
-					grep { $bki_attr{$_} eq 'bool' } @attnames);
+					grep { $bki_attr{$_}{type} eq 'bool' } @attnames);
 				push @{ $schemapg_entries{$table_name} }, '{ '
 				  . join(
 					', ',             grep { defined $_ }
@@ -223,13 +242,13 @@ foreach my $catname (@{ $catalogs->{names} })
 			{
 				$attnum = 0;
 				my @SYS_ATTRS = (
-					{ ctid     => 'tid' },
-					{ oid      => 'oid' },
-					{ xmin     => 'xid' },
-					{ cmin     => 'cid' },
-					{ xmax     => 'xid' },
-					{ cmax     => 'cid' },
-					{ tableoid => 'oid' });
+					{ name => 'ctid', type => 'tid' },
+					{ name => 'oid', type => 'oid' },
+					{ name => 'xmin', type => 'xid' },
+					{ name => 'cmin', type=> 'cid' },
+					{ name => 'xmax', type=> 'xid' },
+					{ name => 'cmax', type => 'cid' },
+					{ name => 'tableoid', type => 'oid' });
 				foreach my $attr (@SYS_ATTRS)
 				{
 					$attnum--;
@@ -326,7 +345,8 @@ exit 0;
 sub emit_pgattr_row
 {
 	my ($table_name, $attr, $priornotnull) = @_;
-	my ($attname, $atttype) = %$attr;
+	my $attname = $attr->{name};
+	my $atttype = $attr->{type};
 	my %row;
 
 	$row{attrelid} = $catalogs->{$table_name}->{relation_oid};
@@ -354,11 +374,20 @@ sub emit_pgattr_row
 			$row{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
 			$row{attcollation} = $type->{typcollation};
 
-			# attnotnull must be set true if the type is fixed-width and
-			# prior columns are too --- compare DefineAttr in bootstrap.c.
-			# oidvector and int2vector are also treated as not-nullable.
-			if ($priornotnull)
+			if (defined $attr->{forcenotnull})
+			{
+				$row{attnotnull} = 't';
+			}
+			elsif (defined $attr->{forcenull})
+			{
+				$row{attnotnull} = 'f';
+			}
+			elsif ($priornotnull)
 			{
+				# 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.
 				$row{attnotnull} =
 				    $type->{typname} eq 'oidvector'   ? 't'
 				  : $type->{typname} eq 'int2vector'  ? 't'
diff --git a/src/backend/utils/Gen_fmgrtab.pl b/src/backend/utils/Gen_fmgrtab.pl
index 8b71864..f5cc265 100644
--- a/src/backend/utils/Gen_fmgrtab.pl
+++ b/src/backend/utils/Gen_fmgrtab.pl
@@ -52,7 +52,7 @@ my @fmgr = ();
 my @attnames;
 foreach my $column (@{ $catalogs->{pg_proc}->{columns} })
 {
-	push @attnames, keys %$column;
+	push @attnames, $column->{name};
 }
 
 my $data = $catalogs->{pg_proc}->{data};
diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h
index be4430a..f9cbc13 100644
--- a/src/include/bootstrap/bootstrap.h
+++ b/src/include/bootstrap/bootstrap.h
@@ -23,6 +23,10 @@
  */
 #define MAXATTR 40
 
+#define BOOTCOL_NULL_AUTO			1
+#define BOOTCOL_NULL_FORCE_NULL		2
+#define BOOTCOL_NULL_FORCE_NOT_NULL	3
+
 extern Relation boot_reldesc;
 extern Form_pg_attribute attrtypes[MAXATTR];
 extern int	numattr;
@@ -35,7 +39,7 @@ extern void err_out(void);
 extern void closerel(char *name);
 extern void boot_openrel(char *name);
 
-extern void DefineAttr(char *name, char *type, int attnum);
+extern void DefineAttr(char *name, char *type, int attnum, int nullness);
 extern void InsertOneTuple(Oid objectid);
 extern void InsertOneValue(char *value, int i);
 extern void InsertOneNull(int i);
diff --git a/src/include/catalog/genbki.h b/src/include/catalog/genbki.h
index 5d6039d..cebf51d 100644
--- a/src/include/catalog/genbki.h
+++ b/src/include/catalog/genbki.h
@@ -28,6 +28,8 @@
 #define BKI_WITHOUT_OIDS
 #define BKI_ROWTYPE_OID(oid)
 #define BKI_SCHEMA_MACRO
+#define BKI_FORCE_NULL
+#define BKI_FORCE_NOT_NULL
 
 /*
  * This is never defined; it's here only for documentation.
-- 
2.3.0.149.gf3f4077.dirty

