Commit 49c784ec authored by Alvaro Herrera's avatar Alvaro Herrera

Remove hard-coded schema knowledge about pg_attribute from genbki.pl

Add the ability to label a column's default value in the catalog header,
and implement this for pg_attribute.  A new function in Catalog.pm is
used to fill in a tuple with defaults.  The build process will complain
loudly if a catalog entry is incomplete,

Commit 8137f2c3 labeled variable length columns for the C preprocessor.
Expose that label to genbki.pl so we can exclude those columns from schema
macros in a general fashion. Also, format schema macro entries according
to their types.

This means slightly less code maintenance, but more importantly it's a
proving ground for mechanisms intended to be used in later commits.

While at it, I (Álvaro) couldn't resist making some changes in
genbki.pl: rename some functions to actually indicate their purpose
instead of actively misleading onlookers; and don't iterate on the whole
of pg_type to find the entry for each catalog row, using a hash instead
of an array.

Author: John Naylor, some changes by Álvaro Herrera
Discussion: https://postgr.es/m/CAJVSVGVJHwD8sfDfZW9TbCHWKf=C1YDRM-rF=2JenRU_y+VcFg@mail.gmail.com
parent bdb70c12
...@@ -37,6 +37,8 @@ sub Catalogs ...@@ -37,6 +37,8 @@ sub Catalogs
foreach my $input_file (@_) foreach my $input_file (@_)
{ {
my %catalog; my %catalog;
my $is_varlen = 0;
$catalog{columns} = []; $catalog{columns} = [];
$catalog{data} = []; $catalog{data} = [];
...@@ -164,7 +166,11 @@ sub Catalogs ...@@ -164,7 +166,11 @@ sub Catalogs
elsif ($declaring_attributes) elsif ($declaring_attributes)
{ {
next if (/^{|^$/); next if (/^{|^$/);
next if (/^#/); if (/^#/)
{
$is_varlen = 1 if /^#ifdef\s+CATALOG_VARLEN/;
next;
}
if (/^}/) if (/^}/)
{ {
undef $declaring_attributes; undef $declaring_attributes;
...@@ -172,8 +178,12 @@ sub Catalogs ...@@ -172,8 +178,12 @@ sub Catalogs
else else
{ {
my %column; my %column;
my ($atttype, $attname, $attopt) = split /\s+/, $_; my @attopts = split /\s+/, $_;
die "parse error ($input_file)" unless $attname; my $atttype = shift @attopts;
my $attname = shift @attopts;
die "parse error ($input_file)"
unless ($attname and $atttype);
if (exists $RENAME_ATTTYPE{$atttype}) if (exists $RENAME_ATTTYPE{$atttype})
{ {
$atttype = $RENAME_ATTTYPE{$atttype}; $atttype = $RENAME_ATTTYPE{$atttype};
...@@ -181,13 +191,14 @@ sub Catalogs ...@@ -181,13 +191,14 @@ sub Catalogs
if ($attname =~ /(.*)\[.*\]/) # array attribute if ($attname =~ /(.*)\[.*\]/) # array attribute
{ {
$attname = $1; $attname = $1;
$atttype .= '[]'; # variable-length only $atttype .= '[]';
} }
$column{type} = $atttype; $column{type} = $atttype;
$column{name} = $attname; $column{name} = $attname;
$column{is_varlen} = 1 if $is_varlen;
if (defined $attopt) foreach my $attopt (@attopts)
{ {
if ($attopt eq 'BKI_FORCE_NULL') if ($attopt eq 'BKI_FORCE_NULL')
{ {
...@@ -197,11 +208,20 @@ sub Catalogs ...@@ -197,11 +208,20 @@ sub Catalogs
{ {
$column{forcenotnull} = 1; $column{forcenotnull} = 1;
} }
elsif ($attopt =~ /BKI_DEFAULT\((\S+)\)/)
{
$column{default} = $1;
}
else else
{ {
die die
"unknown column option $attopt on column $attname"; "unknown column option $attopt on column $attname";
} }
if ($column{forcenull} and $column{forcenotnull})
{
die "$attname is forced both null and not null";
}
} }
push @{ $catalog{columns} }, \%column; push @{ $catalog{columns} }, \%column;
} }
...@@ -235,6 +255,46 @@ sub SplitDataLine ...@@ -235,6 +255,46 @@ sub SplitDataLine
return @result; 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) = @_;
my @missing_fields;
my $msg;
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
{
# Failed to find a value.
push @missing_fields, $attname;
}
}
if (@missing_fields)
{
$msg = "Missing values for: " . join(', ', @missing_fields);
$msg .= "\nShowing other values for context:\n";
while (my($key, $value) = each %$row)
{
$msg .= "$key => $value, ";
}
}
return $msg;
}
# Rename temporary files to final names. # Rename temporary files to final names.
# Call this function with the final file name and the .tmp extension # Call this function with the final file name and the .tmp extension
# Note: recommended extension is ".tmp$$", so that parallel make steps # Note: recommended extension is ".tmp$$", so that parallel make steps
......
...@@ -105,7 +105,7 @@ print $bki "# PostgreSQL $major_version\n"; ...@@ -105,7 +105,7 @@ print $bki "# PostgreSQL $major_version\n";
my %schemapg_entries; my %schemapg_entries;
my @tables_needing_macros; my @tables_needing_macros;
my %regprocoids; my %regprocoids;
my @types; my %types;
# produce output, one catalog at a time # produce output, one catalog at a time
foreach my $catname (@{ $catalogs->{names} }) foreach my $catname (@{ $catalogs->{names} })
...@@ -119,7 +119,6 @@ foreach my $catname (@{ $catalogs->{names} }) ...@@ -119,7 +119,6 @@ foreach my $catname (@{ $catalogs->{names} })
. $catalog->{without_oids} . $catalog->{without_oids}
. $catalog->{rowtype_oid} . "\n"; . $catalog->{rowtype_oid} . "\n";
my %bki_attr;
my @attnames; my @attnames;
my $first = 1; my $first = 1;
...@@ -129,7 +128,6 @@ foreach my $catname (@{ $catalogs->{names} }) ...@@ -129,7 +128,6 @@ foreach my $catname (@{ $catalogs->{names} })
{ {
my $attname = $column->{name}; my $attname = $column->{name};
my $atttype = $column->{type}; my $atttype = $column->{type};
$bki_attr{$attname} = $column;
push @attnames, $attname; push @attnames, $attname;
if (!$first) if (!$first)
...@@ -211,7 +209,7 @@ foreach my $catname (@{ $catalogs->{names} }) ...@@ -211,7 +209,7 @@ foreach my $catname (@{ $catalogs->{names} })
{ {
my %type = %bki_values; my %type = %bki_values;
$type{oid} = $row->{oid}; $type{oid} = $row->{oid};
push @types, \%type; $types{ $type{typname} } = \%type;
} }
# Write to postgres.bki # Write to postgres.bki
...@@ -253,28 +251,24 @@ foreach my $catname (@{ $catalogs->{names} }) ...@@ -253,28 +251,24 @@ foreach my $catname (@{ $catalogs->{names} })
# Generate entries for user attributes. # Generate entries for user attributes.
my $attnum = 0; my $attnum = 0;
my $priornotnull = 1; my $priornotnull = 1;
my @user_attrs = @{ $table->{columns} }; foreach my $attr (@{ $table->{columns} })
foreach my $attr (@user_attrs)
{ {
$attnum++; $attnum++;
my $row = emit_pgattr_row($table_name, $attr, $priornotnull); my %row;
$row->{attnum} = $attnum; $row{attnum} = $attnum;
$row->{attstattarget} = '-1'; $row{attrelid} = $table->{relation_oid};
$priornotnull &= ($row->{attnotnull} eq 't');
morph_row_for_pgattr(\%row, $schema, $attr, $priornotnull);
$priornotnull &= ($row{attnotnull} eq 't');
# If it's bootstrapped, put an entry in postgres.bki. # If it's bootstrapped, put an entry in postgres.bki.
if ($table->{bootstrap}) print_bki_insert(\%row, @attnames) if $table->{bootstrap};
{
bki_insert($row, @attnames);
}
# Store schemapg entries for later. # Store schemapg entries for later.
$row = morph_row_for_schemapg(\%row, $schema);
emit_schemapg_row($row,
grep { $bki_attr{$_}{type} eq 'bool' } @attnames);
push @{ $schemapg_entries{$table_name} }, push @{ $schemapg_entries{$table_name} },
sprintf "{ %s }", sprintf "{ %s }",
join(', ', grep { defined $_ } @{$row}{@attnames}); join(', ', grep { defined $_ } @row{@attnames});
} }
# Generate entries for system attributes. # Generate entries for system attributes.
...@@ -293,16 +287,18 @@ foreach my $catname (@{ $catalogs->{names} }) ...@@ -293,16 +287,18 @@ foreach my $catname (@{ $catalogs->{names} })
foreach my $attr (@SYS_ATTRS) foreach my $attr (@SYS_ATTRS)
{ {
$attnum--; $attnum--;
my $row = emit_pgattr_row($table_name, $attr, 1); my %row;
$row->{attnum} = $attnum; $row{attnum} = $attnum;
$row->{attstattarget} = '0'; $row{attrelid} = $table->{relation_oid};
$row{attstattarget} = '0';
# Omit the oid column if the catalog doesn't have them # Omit the oid column if the catalog doesn't have them
next next
if $table->{without_oids} if $table->{without_oids}
&& $row->{attname} eq 'oid'; && $attr->{name} eq 'oid';
bki_insert($row, @attnames); morph_row_for_pgattr(\%row, $schema, $attr, 1);
print_bki_insert(\%row, @attnames);
} }
} }
} }
...@@ -379,130 +375,122 @@ exit 0; ...@@ -379,130 +375,122 @@ exit 0;
#################### Subroutines ######################## #################### Subroutines ########################
# Given a system catalog name and a reference to a key-value pair corresponding # Given $pgattr_schema (the pg_attribute schema for a catalog sufficient for
# to the name and type of a column, generate a reference to a hash that # AddDefaultValues), $attr (the description of a catalog row), and
# represents a pg_attribute entry. We must also be told whether preceding # $priornotnull (whether all prior attributes in this catalog are not null),
# columns were all not-null. # modify the $row hashref for print_bki_insert. This includes setting data
sub emit_pgattr_row # 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 ($table_name, $attr, $priornotnull) = @_; my ($row, $pgattr_schema, $attr, $priornotnull) = @_;
my $attname = $attr->{name}; my $attname = $attr->{name};
my $atttype = $attr->{type}; my $atttype = $attr->{type};
my %row;
$row{attrelid} = $catalogs->{$table_name}->{relation_oid}; $row->{attname} = $attname;
$row{attname} = $attname;
# Adjust type name for arrays: foo[] becomes _foo # Adjust type name for arrays: foo[] becomes _foo, so we can look it up in
# so we can look it up in pg_type # pg_type
if ($atttype =~ /(.+)\[\]$/) $atttype = '_' . $1 if $atttype =~ /(.+)\[\]$/;
{
$atttype = '_' . $1;
}
# Copy the type data from pg_type, and add some type-dependent items # Copy the type data from pg_type, and add some type-dependent items
foreach my $type (@types) my $type = $types{$atttype};
{
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->{atttypid} = $type->{oid};
$row{attndims} = $type->{typcategory} eq 'A' ? '1' : '0'; $row->{attlen} = $type->{typlen};
$row{attcollation} = $type->{typcollation}; $row->{attbyval} = $type->{typbyval};
$row->{attstorage} = $type->{typstorage};
$row->{attalign} = $type->{typalign};
if (defined $attr->{forcenotnull}) # set attndims if it's an array type
{ $row->{attndims} = $type->{typcategory} eq 'A' ? '1' : '0';
$row{attnotnull} = 't'; $row->{attcollation} = $type->{typcollation};
}
elsif (defined $attr->{forcenull})
{
$row{attnotnull} = 'f';
}
elsif ($priornotnull)
{
# attnotnull will automatically be set if the type is if (defined $attr->{forcenotnull})
# fixed-width and prior columns are all NOT NULL --- {
# compare DefineAttr in bootstrap.c. oidvector and $row->{attnotnull} = 't';
# int2vector are also treated as not-nullable. }
$row{attnotnull} = elsif (defined $attr->{forcenull})
$type->{typname} eq 'oidvector' ? 't' {
: $type->{typname} eq 'int2vector' ? 't' $row->{attnotnull} = 'f';
: $type->{typlen} eq 'NAMEDATALEN' ? 't'
: $type->{typlen} > 0 ? 't'
: 'f';
}
else
{
$row{attnotnull} = 'f';
}
last;
}
} }
elsif ($priornotnull)
{
# Add in default values for pg_attribute # attnotnull will automatically be set if the type is
my %PGATTR_DEFAULTS = ( # fixed-width and prior columns are all NOT NULL ---
attcacheoff => '-1', # compare DefineAttr in bootstrap.c. oidvector and
atttypmod => '-1', # int2vector are also treated as not-nullable.
atthasdef => 'f', $row->{attnotnull} =
attidentity => '', $type->{typname} eq 'oidvector' ? 't'
attisdropped => 'f', : $type->{typname} eq 'int2vector' ? 't'
attislocal => 't', : $type->{typlen} eq 'NAMEDATALEN' ? 't'
attinhcount => '0', : $type->{typlen} > 0 ? 't'
attacl => '_null_', : 'f';
attoptions => '_null_', }
attfdwoptions => '_null_'); else
return { %PGATTR_DEFAULTS, %row }; {
$row->{attnotnull} = 'f';
}
my $error = Catalog::AddDefaultValues($row, $pgattr_schema);
if ($error)
{
die "Failed to form full tuple for pg_attribute: ", $error;
}
} }
# Write a pg_attribute entry to postgres.bki # Write a pg_attribute entry to postgres.bki
sub bki_insert sub print_bki_insert
{ {
my $row = shift; my $row = shift;
my @attnames = @_; my @attnames = @_;
my $oid = $row->{oid} ? "OID = $row->{oid} " : ''; my $oid = $row->{oid} ? "OID = $row->{oid} " : '';
my $bki_values = join ' ', map { $_ eq '' ? '""' : $_ } map $row->{$_}, my $bki_values = join ' ', @{$row}{@attnames};
@attnames;
printf $bki "insert %s( %s )\n", $oid, $bki_values; printf $bki "insert %s( %s )\n", $oid, $bki_values;
} }
# Given a row reference, modify it so that it becomes a valid entry for
# a catalog schema declaration in schemapg.h.
#
# The field values of a Schema_pg_xxx declaration are similar, but not # The field values of a Schema_pg_xxx declaration are similar, but not
# quite identical, to the corresponding values in postgres.bki. # quite identical, to the corresponding values in postgres.bki.
sub emit_schemapg_row sub morph_row_for_schemapg
{ {
my $row = shift; my $row = shift;
my @bool_attrs = @_; my $pgattr_schema = shift;
# Replace empty string by zero char constant foreach my $column (@$pgattr_schema)
$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} = my $attname = $column->{name};
$row->{$attr} eq 't' ? 'true' my $atttype = $column->{type};
: $row->{$attr} eq 'f' ? 'false'
: $row->{$attr}; # Some data types have special formatting rules.
if ($atttype eq 'name')
{
# add {" ... "} quoting
$row->{$attname} = sprintf(qq'{"%s"}', $row->{$attname});
}
elsif ($atttype eq 'char')
{
# Replace empty string by zero char constant; add single quotes
$row->{$attname} = '\0' if $row->{$attname} eq q|""|;
$row->{$attname} = sprintf("'%s'", $row->{$attname});
}
# Expand booleans from 'f'/'t' to 'false'/'true'.
# Some values might be other macros (eg FLOAT4PASSBYVAL),
# don't change.
elsif ($atttype eq 'bool')
{
$row->{$attname} = 'true' if $row->{$attname} eq 't';
$row->{$attname} = 'false' if $row->{$attname} eq 'f';
}
# 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->{$attname} if $column->{is_varlen};
} }
return $row;
} }
sub usage sub usage
......
...@@ -31,6 +31,9 @@ ...@@ -31,6 +31,9 @@
#define BKI_FORCE_NULL #define BKI_FORCE_NULL
#define BKI_FORCE_NOT_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. * This is never defined; it's here only for documentation.
* *
......
...@@ -54,7 +54,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -54,7 +54,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
* that no value has been explicitly set for this column, so ANALYZE * that no value has been explicitly set for this column, so ANALYZE
* should use the default setting. * should use the default setting.
*/ */
int32 attstattarget; int32 attstattarget BKI_DEFAULT(-1);
/* /*
* attlen is a copy of the typlen field from pg_type for this attribute. * attlen is a copy of the typlen field from pg_type for this attribute.
...@@ -90,7 +90,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -90,7 +90,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
* descriptor, we may then update attcacheoff in the copies. This speeds * descriptor, we may then update attcacheoff in the copies. This speeds
* up the attribute walking process. * up the attribute walking process.
*/ */
int32 attcacheoff; int32 attcacheoff BKI_DEFAULT(-1);
/* /*
* atttypmod records type-specific data supplied at table creation time * atttypmod records type-specific data supplied at table creation time
...@@ -98,7 +98,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -98,7 +98,7 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
* type-specific input and output functions as the third argument. The * type-specific input and output functions as the third argument. The
* value will generally be -1 for types that do not need typmod. * value will generally be -1 for types that do not need typmod.
*/ */
int32 atttypmod; int32 atttypmod BKI_DEFAULT(-1);
/* /*
* attbyval is a copy of the typbyval field from pg_type for this * attbyval is a copy of the typbyval field from pg_type for this
...@@ -131,13 +131,13 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -131,13 +131,13 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
bool attnotnull; bool attnotnull;
/* Has DEFAULT value or not */ /* Has DEFAULT value or not */
bool atthasdef; bool atthasdef BKI_DEFAULT(f);
/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */ /* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
char attidentity; char attidentity BKI_DEFAULT("");
/* Is dropped (ie, logically invisible) or not */ /* Is dropped (ie, logically invisible) or not */
bool attisdropped; bool attisdropped BKI_DEFAULT(f);
/* /*
* This flag specifies whether this column has ever had a local * This flag specifies whether this column has ever had a local
...@@ -148,10 +148,10 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -148,10 +148,10 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
* not dropped by a parent's DROP COLUMN even if this causes the column's * not dropped by a parent's DROP COLUMN even if this causes the column's
* attinhcount to become zero. * attinhcount to become zero.
*/ */
bool attislocal; bool attislocal BKI_DEFAULT(t);
/* Number of times inherited from direct parent relation(s) */ /* Number of times inherited from direct parent relation(s) */
int32 attinhcount; int32 attinhcount BKI_DEFAULT(0);
/* attribute's collation */ /* attribute's collation */
Oid attcollation; Oid attcollation;
...@@ -160,13 +160,13 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK ...@@ -160,13 +160,13 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
/* NOTE: The following fields are not present in tuple descriptors. */ /* NOTE: The following fields are not present in tuple descriptors. */
/* Column-level access permissions */ /* Column-level access permissions */
aclitem attacl[1]; aclitem attacl[1] BKI_DEFAULT(_null_);
/* Column-level options */ /* Column-level options */
text attoptions[1]; text attoptions[1] BKI_DEFAULT(_null_);
/* Column-level FDW options */ /* Column-level FDW options */
text attfdwoptions[1]; text attfdwoptions[1] BKI_DEFAULT(_null_);
#endif #endif
} FormData_pg_attribute; } FormData_pg_attribute;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment