 src/backend/catalog/Catalog.pm     |  49 +++++++++++-
 src/backend/catalog/genbki.pl      | 158 ++++++++++++++++++-------------------
 src/include/catalog/genbki.h       |   3 +
 src/include/catalog/pg_attribute.h |  22 +++---
 4 files changed, 140 insertions(+), 92 deletions(-)

diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
new file mode 100644
index 6bc14d2..246aa36
*** a/src/backend/catalog/Catalog.pm
--- b/src/backend/catalog/Catalog.pm
*************** sub Catalogs
*** 44,49 ****
--- 44,50 ----
  		my %catalog;
  		$catalog{columns} = [];
  		$catalog{data}    = [];
+ 		my $is_varlen     = 0;
  
  		open(my $ifh, '<', $input_file) || die "$input_file: $!";
  
*************** sub Catalogs
*** 169,175 ****
  			elsif ($declaring_attributes)
  			{
  				next if (/^{|^$/);
! 				next if (/^#/);
  				if (/^}/)
  				{
  					undef $declaring_attributes;
--- 170,183 ----
  			elsif ($declaring_attributes)
  			{
  				next if (/^{|^$/);
! 				if (/^#/)
! 				{
! 					if (/^#ifdef\s+CATALOG_VARLEN/)
! 					{
! 						$is_varlen = 1;
! 					}
! 					next;
! 				}
  				if (/^}/)
  				{
  					undef $declaring_attributes;
*************** sub Catalogs
*** 177,182 ****
--- 185,194 ----
  				else
  				{
  					my %column;
+ 					if ($is_varlen)
+ 					{
+ 						$column{is_varlen} = 1;
+ 					}
  					my ($atttype, $attname, $attopt) = split /\s+/, $_;
  					die "parse error ($input_file)" unless $attname;
  					if (exists $RENAME_ATTTYPE{$atttype})
*************** sub Catalogs
*** 186,192 ****
  					if ($attname =~ /(.*)\[.*\]/)    # array attribute
  					{
  						$attname = $1;
! 						$atttype .= '[]';            # variable-length only
  					}
  
  					$column{type} = $atttype;
--- 198,204 ----
  					if ($attname =~ /(.*)\[.*\]/)    # array attribute
  					{
  						$attname = $1;
! 						$atttype .= '[]';
  					}
  
  					$column{type} = $atttype;
*************** sub Catalogs
*** 202,207 ****
--- 214,223 ----
  						{
  							$column{forcenotnull} = 1;
  						}
+ 						elsif ($attopt =~ /BKI_DEFAULT\((\S+)\)/)
+ 						{
+ 							$column{default} = $1;
+ 						}
  						else
  						{
  							die
*************** sub SplitDataLine
*** 240,245 ****
--- 256,290 ----
  	return @result;
  }
  
+ # Fill in default values of a record using the given schema. It's the
+ # caller's responsibility to specify other values beforehand.
+ sub AddDefaultValues
+ {
+ 	my ($row, $schema, $catname) = @_;
+ 
+ 	die "Schema undefined for $catname\n"
+ 	  if !defined $schema;
+ 
+ 	foreach my $column (@$schema)
+ 	{
+ 		my $attname = $column->{name};
+ 		my $atttype = $column->{type};
+ 
+ 		if (defined $row->{$attname})
+ 		{
+ 			;
+ 		}
+ 		elsif (defined $column->{default})
+ 		{
+ 			$row->{$attname} = $column->{default};
+ 		}
+ 		else
+ 		{
+ 			die "Unspecified value in $catname.$attname\n";
+ 		}
+ 	}
+ }
+ 
  # Rename temporary files to final names.
  # Call this function with the final file name and the .tmp extension
  # Note: recommended extension is ".tmp$$", so that parallel make steps
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
new file mode 100644
index 4bd614f..1876399
*** a/src/backend/catalog/genbki.pl
--- b/src/backend/catalog/genbki.pl
*************** foreach my $catname (@{ $catalogs->{name
*** 119,125 ****
  	  . $catalog->{without_oids}
  	  . $catalog->{rowtype_oid} . "\n";
  
- 	my %bki_attr;
  	my @attnames;
  	my $first = 1;
  
--- 119,124 ----
*************** foreach my $catname (@{ $catalogs->{name
*** 129,135 ****
  	{
  		my $attname = $column->{name};
  		my $atttype = $column->{type};
- 		$bki_attr{$attname} = $column;
  		push @attnames, $attname;
  
  		if (!$first)
--- 128,133 ----
*************** foreach my $catname (@{ $catalogs->{name
*** 257,281 ****
  			foreach my $attr (@user_attrs)
  			{
  				$attnum++;
! 				my $row = emit_pgattr_row($table_name, $attr, $priornotnull);
! 				$row->{attnum}        = $attnum;
! 				$row->{attstattarget} = '-1';
! 				$priornotnull &= ($row->{attnotnull} eq 't');
  
  				# If it's bootstrapped, put an entry in postgres.bki.
  				if ($table->{bootstrap})
  				{
! 					bki_insert($row, @attnames);
  				}
  
  				# Store schemapg entries for later.
! 				$row =
! 				  emit_schemapg_row($row,
! 					grep { $bki_attr{$_}{type} eq 'bool' } @attnames);
! 				push @{ $schemapg_entries{$table_name} }, '{ '
! 				  . join(
! 					', ',             grep { defined $_ }
! 					  map $row->{$_}, @attnames) . ' }';
  			}
  
  			# Generate entries for system attributes.
--- 255,279 ----
  			foreach my $attr (@user_attrs)
  			{
  				$attnum++;
! 				my %row;
! 				$row{attnum}   = $attnum;
! 				$row{attrelid} = $table->{relation_oid};
! 
! 				emit_pgattr_row(\%row, $attr, $priornotnull, $schema);
! 				$priornotnull &= ($row{attnotnull} eq 't');
  
  				# If it's bootstrapped, put an entry in postgres.bki.
  				if ($table->{bootstrap})
  				{
! 					bki_insert(\%row, @attnames);
  				}
  
  				# Store schemapg entries for later.
! 				emit_schemapg_row(\%row, $schema);
! 				push @{ $schemapg_entries{$table_name} },
! 				    '{ '
! 				  . join(', ', grep { defined $_ } @row{@attnames})
! 				  . ' }';
  			}
  
  			# Generate entries for system attributes.
*************** foreach my $catname (@{ $catalogs->{name
*** 294,309 ****
  				foreach my $attr (@SYS_ATTRS)
  				{
  					$attnum--;
! 					my $row = emit_pgattr_row($table_name, $attr, 1);
! 					$row->{attnum}        = $attnum;
! 					$row->{attstattarget} = '0';
  
  					# some catalogs don't have oids
  					next
  					  if $table->{without_oids}
! 						  && $row->{attname} eq 'oid';
  
! 					bki_insert($row, @attnames);
  				}
  			}
  		}
--- 292,310 ----
  				foreach my $attr (@SYS_ATTRS)
  				{
  					$attnum--;
! 					my %row;
! 					$row{attnum}        = $attnum;
! 					$row{attrelid}      = $table->{relation_oid};
! 					$row{attstattarget} = '0';
! 
! 					emit_pgattr_row(\%row, $attr, 1, $schema);
  
  					# some catalogs don't have oids
  					next
  					  if $table->{without_oids}
! 						  && $row{attname} eq 'oid';
  
! 					bki_insert(\%row, @attnames);
  				}
  			}
  		}
*************** exit 0;
*** 380,398 ****
  #################### Subroutines ########################
  
  
! # Given a system catalog name and a reference to a key-value pair corresponding
! # to the name and type of a column, generate a reference to a hash that
! # represents a pg_attribute entry.  We must also be told whether preceding
  # columns were all not-null.
  sub emit_pgattr_row
  {
! 	my ($table_name, $attr, $priornotnull) = @_;
  	my $attname = $attr->{name};
  	my $atttype = $attr->{type};
- 	my %row;
  
! 	$row{attrelid} = $catalogs->{$table_name}->{relation_oid};
! 	$row{attname}  = $attname;
  
  	# Adjust type name for arrays: foo[] becomes _foo
  	# so we can look it up in pg_type
--- 381,397 ----
  #################### Subroutines ########################
  
  
! # Given the schema of pg_attribute, generate an entry for it using information
! # about the attribute it describes.  Any value that is not handled here
! # must be supplied by the caller. We must also be told whether preceding
  # columns were all not-null.
  sub emit_pgattr_row
  {
! 	my ($row, $attr, $priornotnull, $pgattr_schema) = @_;
  	my $attname = $attr->{name};
  	my $atttype = $attr->{type};
  
! 	$row->{attname} = $attname;
  
  	# Adjust type name for arrays: foo[] becomes _foo
  	# so we can look it up in pg_type
*************** sub emit_pgattr_row
*** 406,428 ****
  	{
  		if (defined $type->{typname} && $type->{typname} eq $atttype)
  		{
! 			$row{atttypid}   = $type->{oid};
! 			$row{attlen}     = $type->{typlen};
! 			$row{attbyval}   = $type->{typbyval};
! 			$row{attstorage} = $type->{typstorage};
! 			$row{attalign}   = $type->{typalign};
  
  			# set attndims if it's an array type
! 			$row{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
! 			$row{attcollation} = $type->{typcollation};
  
  			if (defined $attr->{forcenotnull})
  			{
! 				$row{attnotnull} = 't';
  			}
  			elsif (defined $attr->{forcenull})
  			{
! 				$row{attnotnull} = 'f';
  			}
  			elsif ($priornotnull)
  			{
--- 405,427 ----
  	{
  		if (defined $type->{typname} && $type->{typname} eq $atttype)
  		{
! 			$row->{atttypid}   = $type->{oid};
! 			$row->{attlen}     = $type->{typlen};
! 			$row->{attbyval}   = $type->{typbyval};
! 			$row->{attstorage} = $type->{typstorage};
! 			$row->{attalign}   = $type->{typalign};
  
  			# set attndims if it's an array type
! 			$row->{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
! 			$row->{attcollation} = $type->{typcollation};
  
  			if (defined $attr->{forcenotnull})
  			{
! 				$row->{attnotnull} = 't';
  			}
  			elsif (defined $attr->{forcenull})
  			{
! 				$row->{attnotnull} = 'f';
  			}
  			elsif ($priornotnull)
  			{
*************** sub emit_pgattr_row
*** 431,437 ****
  				# 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'
  				  : $type->{typlen}  eq 'NAMEDATALEN' ? 't'
--- 430,436 ----
  				# 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'
  				  : $type->{typlen}  eq 'NAMEDATALEN' ? 't'
*************** sub emit_pgattr_row
*** 440,464 ****
  			}
  			else
  			{
! 				$row{attnotnull} = 'f';
  			}
  			last;
  		}
  	}
! 
! 	# Add in default values for pg_attribute
! 	my %PGATTR_DEFAULTS = (
! 		attcacheoff   => '-1',
! 		atttypmod     => '-1',
! 		atthasdef     => 'f',
! 		attidentity   => '',
! 		attisdropped  => 'f',
! 		attislocal    => 't',
! 		attinhcount   => '0',
! 		attacl        => '_null_',
! 		attoptions    => '_null_',
! 		attfdwoptions => '_null_');
! 	return { %PGATTR_DEFAULTS, %row };
  }
  
  # Write a pg_attribute entry to postgres.bki
--- 439,450 ----
  			}
  			else
  			{
! 				$row->{attnotnull} = 'f';
  			}
  			last;
  		}
  	}
! 	Catalog::AddDefaultValues($row, $pgattr_schema, 'pg_attribute');
  }
  
  # Write a pg_attribute entry to postgres.bki
*************** sub bki_insert
*** 467,474 ****
  	my $row        = shift;
  	my @attnames   = @_;
  	my $oid        = $row->{oid} ? "OID = $row->{oid} " : '';
! 	my $bki_values = join ' ', map { $_ eq '' ? '""' : $_ } map $row->{$_},
! 	  @attnames;
  	printf $bki "insert %s( %s )\n", $oid, $bki_values;
  }
  
--- 453,459 ----
  	my $row        = shift;
  	my @attnames   = @_;
  	my $oid        = $row->{oid} ? "OID = $row->{oid} " : '';
! 	my $bki_values = join ' ', map $row->{$_}, @attnames;
  	printf $bki "insert %s( %s )\n", $oid, $bki_values;
  }
  
*************** sub bki_insert
*** 476,509 ****
  # quite identical, to the corresponding values in postgres.bki.
  sub emit_schemapg_row
  {
! 	my $row        = shift;
! 	my @bool_attrs = @_;
  
! 	# Replace empty string by zero char constant
! 	$row->{attidentity} ||= '\0';
  
! 	# Supply appropriate quoting for these fields.
! 	$row->{attname}     = q|{"| . $row->{attname} . q|"}|;
! 	$row->{attstorage}  = q|'| . $row->{attstorage} . q|'|;
! 	$row->{attalign}    = q|'| . $row->{attalign} . q|'|;
! 	$row->{attidentity} = q|'| . $row->{attidentity} . q|'|;
  
! 	# We don't emit initializers for the variable length fields at all.
! 	# Only the fixed-size portions of the descriptors are ever used.
! 	delete $row->{attacl};
! 	delete $row->{attoptions};
! 	delete $row->{attfdwoptions};
  
! 	# Expand booleans from 'f'/'t' to 'false'/'true'.
! 	# Some values might be other macros (eg FLOAT4PASSBYVAL), don't change.
! 	foreach my $attr (@bool_attrs)
! 	{
! 		$row->{$attr} =
! 		    $row->{$attr} eq 't' ? 'true'
! 		  : $row->{$attr} eq 'f' ? 'false'
! 		  :                        $row->{$attr};
  	}
- 	return $row;
  }
  
  sub usage
--- 461,509 ----
  # quite identical, to the corresponding values in postgres.bki.
  sub emit_schemapg_row
  {
! 	my $row           = shift;
! 	my $pgattr_schema = shift;
  
! 	foreach my $column (@$pgattr_schema)
! 	{
! 		my $pgattr_name = $column->{name};
! 		my $pgattr_type = $column->{type};
  
! 		# Supply appropriate quoting for these fields.
! 		if ($pgattr_type eq 'name')
! 		{
! 			$row->{$pgattr_name} = q|{"| . $row->{$pgattr_name} . q|"}|;
! 		}
! 		elsif ($pgattr_type eq 'char')
! 		{
  
! 			# Replace empty string by zero char constant
! 			if ($row->{$pgattr_name} eq q|""|)
! 			{
! 				$row->{$pgattr_name} = '\0';
! 			}
  
! 			$row->{$pgattr_name} = q|'| . $row->{$pgattr_name} . q|'|;
! 		}
! 
! 		# Expand booleans from 'f'/'t' to 'false'/'true'.
! 		# Some values might be other macros (eg FLOAT4PASSBYVAL),
! 		# don't change.
! 		elsif ($pgattr_type eq 'bool')
! 		{
! 			$row->{$pgattr_name} =
! 			    $row->{$pgattr_name} eq 't' ? 'true'
! 			  : $row->{$pgattr_name} eq 'f' ? 'false'
! 			  :                               $row->{$pgattr_name};
! 		}
! 
! 		# We don't emit initializers for the variable length fields at all.
! 		# Only the fixed-size portions of the descriptors are ever used.
! 		if ($column->{is_varlen})
! 		{
! 			delete $row->{$pgattr_name};
! 		}
  	}
  }
  
  sub usage
diff --git a/src/include/catalog/genbki.h b/src/include/catalog/genbki.h
new file mode 100644
index a2cb313..71fc579
*** a/src/include/catalog/genbki.h
--- b/src/include/catalog/genbki.h
***************
*** 31,36 ****
--- 31,39 ----
  #define BKI_FORCE_NULL
  #define BKI_FORCE_NOT_NULL
  
+ /* Specifies a default value for a catalog field */
+ #define BKI_DEFAULT(value)
+ 
  /*
   * This is never defined; it's here only for documentation.
   *
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
new file mode 100644
index bcf28e8..5436a90
*** a/src/include/catalog/pg_attribute.h
--- b/src/include/catalog/pg_attribute.h
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 54,60 ****
  	 * that no value has been explicitly set for this column, so ANALYZE
  	 * should use the default setting.
  	 */
! 	int32		attstattarget;
  
  	/*
  	 * attlen is a copy of the typlen field from pg_type for this attribute.
--- 54,60 ----
  	 * that no value has been explicitly set for this column, so ANALYZE
  	 * should use the default setting.
  	 */
! 	int32		attstattarget BKI_DEFAULT(-1);
  
  	/*
  	 * attlen is a copy of the typlen field from pg_type for this attribute.
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 90,96 ****
  	 * descriptor, we may then update attcacheoff in the copies. This speeds
  	 * up the attribute walking process.
  	 */
! 	int32		attcacheoff;
  
  	/*
  	 * atttypmod records type-specific data supplied at table creation time
--- 90,96 ----
  	 * descriptor, we may then update attcacheoff in the copies. This speeds
  	 * up the attribute walking process.
  	 */
! 	int32		attcacheoff BKI_DEFAULT(-1);
  
  	/*
  	 * atttypmod records type-specific data supplied at table creation time
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 98,104 ****
  	 * type-specific input and output functions as the third argument. The
  	 * value will generally be -1 for types that do not need typmod.
  	 */
! 	int32		atttypmod;
  
  	/*
  	 * attbyval is a copy of the typbyval field from pg_type for this
--- 98,104 ----
  	 * type-specific input and output functions as the third argument. The
  	 * value will generally be -1 for types that do not need typmod.
  	 */
! 	int32		atttypmod BKI_DEFAULT(-1);
  
  	/*
  	 * attbyval is a copy of the typbyval field from pg_type for this
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 131,143 ****
  	bool		attnotnull;
  
  	/* Has DEFAULT value or not */
! 	bool		atthasdef;
  
  	/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
! 	char		attidentity;
  
  	/* Is dropped (ie, logically invisible) or not */
! 	bool		attisdropped;
  
  	/*
  	 * This flag specifies whether this column has ever had a local
--- 131,143 ----
  	bool		attnotnull;
  
  	/* Has DEFAULT value or not */
! 	bool		atthasdef BKI_DEFAULT(f);
  
  	/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
! 	char		attidentity BKI_DEFAULT("");
  
  	/* Is dropped (ie, logically invisible) or not */
! 	bool		attisdropped BKI_DEFAULT(f);
  
  	/*
  	 * This flag specifies whether this column has ever had a local
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 148,157 ****
  	 * not dropped by a parent's DROP COLUMN even if this causes the column's
  	 * attinhcount to become zero.
  	 */
! 	bool		attislocal;
  
  	/* Number of times inherited from direct parent relation(s) */
! 	int32		attinhcount;
  
  	/* attribute's collation */
  	Oid			attcollation;
--- 148,157 ----
  	 * not dropped by a parent's DROP COLUMN even if this causes the column's
  	 * attinhcount to become zero.
  	 */
! 	bool		attislocal BKI_DEFAULT(t);
  
  	/* Number of times inherited from direct parent relation(s) */
! 	int32		attinhcount BKI_DEFAULT(0);
  
  	/* attribute's collation */
  	Oid			attcollation;
*************** CATALOG(pg_attribute,1249) BKI_BOOTSTRAP
*** 160,172 ****
  	/* NOTE: The following fields are not present in tuple descriptors. */
  
  	/* Column-level access permissions */
! 	aclitem		attacl[1];
  
  	/* Column-level options */
! 	text		attoptions[1];
  
  	/* Column-level FDW options */
! 	text		attfdwoptions[1];
  #endif
  } FormData_pg_attribute;
  
--- 160,172 ----
  	/* NOTE: The following fields are not present in tuple descriptors. */
  
  	/* Column-level access permissions */
! 	aclitem		attacl[1] BKI_DEFAULT(_null_);
  
  	/* Column-level options */
! 	text		attoptions[1] BKI_DEFAULT(_null_);
  
  	/* Column-level FDW options */
! 	text		attfdwoptions[1] BKI_DEFAULT(_null_);
  #endif
  } FormData_pg_attribute;
  
