1 #----------------------------------------------------------------------
4 # Perl module that extracts info from catalog files into Perl
7 # Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
8 # Portions Copyright (c) 1994, Regents of the University of California
10 # src/backend/catalog/Catalog.pm
12 #----------------------------------------------------------------------
17 use warnings FATAL
=> 'all';
22 # Parses a catalog header file into a data structure describing the schema
26 my $input_file = shift;
28 # There are a few types which are given one name in the C source, but a
29 # different name at the SQL level. These are enumerated here.
30 my %RENAME_ATTTYPE = (
36 'TransactionId' => 'xid',
37 'XLogRecPtr' => 'pg_lsn');
40 my $declaring_attributes = 0;
42 my $is_client_code = 0;
44 $catalog{columns
} = [];
45 $catalog{toasting
} = [];
46 $catalog{indexing
} = [];
47 $catalog{other_oids
} = [];
48 $catalog{foreign_keys
} = [];
49 $catalog{client_code
} = [];
51 open(my $ifh, '<', $input_file) || die "$input_file: $!";
53 # Scan the input file.
57 # Set appropriate flag when we're in certain code sections.
60 $is_varlen = 1 if /^#ifdef\s+CATALOG_VARLEN/;
61 if (/^#ifdef\s+EXPOSE_TO_CLIENT_CODE/)
66 next if !$is_client_code;
71 # Strip C-style comments.
76 # handle multi-line comments properly.
77 my $next_line = <$ifh>;
78 die "$input_file: ends within C-style comment\n"
79 if !defined $next_line;
84 # Strip useless whitespace and trailing semicolons.
91 # Push the data into the appropriate data structure.
92 # Caution: when adding new recognized OID-defining macros,
93 # also update src/include/catalog/renumber_oids.pl.
94 if (/^DECLARE_TOAST\
(\s
*
95 (?
<parent_table
>\w
+),\s
*
97 (?
<toast_index_oid
>\d
+)\s
*
101 push @
{ $catalog{toasting
} }, {%+};
104 /^DECLARE_TOAST_WITH_MACRO\
(\s
*
105 (?
<parent_table
>\w
+),\s
*
106 (?
<toast_oid
>\d
+),\s
*
107 (?
<toast_index_oid
>\d
+),\s
*
108 (?
<toast_oid_macro
>\w
+),\s
*
109 (?
<toast_index_oid_macro
>\w
+)\s
*
113 push @
{ $catalog{toasting
} }, {%+};
116 /^DECLARE_
(UNIQUE_
)?INDEX
(_PKEY
)?\
(\s
*
117 (?
<index_name
>\w
+),\s
*
118 (?
<index_oid
>\d
+),\s
*
119 (?
<index_oid_macro
>\w
+),\s
*
120 (?
<table_name
>\w
+),\s
*
125 push @
{ $catalog{indexing
} },
127 is_unique
=> $1 ?
1 : 0,
128 is_pkey
=> $2 ?
1 : 0,
134 (?
<syscache_name
>\w
+),\s
*
135 (?
<index_name
>\w
+),\s
*
136 (?
<syscache_nbuckets
>\w
+)\s
*
140 push @
{ $catalog{syscaches
} }, {%+};
143 /^DECLARE_OID_DEFINING_MACRO\
(\s
*
144 (?
<other_name
>\w
+),\s
*
149 push @
{ $catalog{other_oids
} }, {%+};
152 /^DECLARE_
(ARRAY_
)?FOREIGN_KEY
(_OPT
)?\
(\s
*
153 \
((?
<fk_cols
>[^)]+)\
),\s
*
155 \
((?
<pk_cols
>[^)]+)\
)\s
*
159 push @
{ $catalog{foreign_keys
} },
161 is_array
=> $1 ?
1 : 0,
162 is_opt
=> $2 ?
1 : 0,
169 (?
<relation_oid
>\d
+),\s
*
170 (?
<relation_oid_macro
>\w
+)\s
*
174 @catalog{ keys %+ } = values %+;
176 $catalog{bootstrap
} = /BKI_BOOTSTRAP/ ?
' bootstrap' : '';
177 $catalog{shared_relation
} =
178 /BKI_SHARED_RELATION/ ?
' shared_relation' : '';
179 if (/BKI_ROWTYPE_OID\
(\s
*
180 (?
<rowtype_oid
>\d
+),\s
*
181 (?
<rowtype_oid_macro
>\w
+)\s
*
185 @catalog{ keys %+ } = values %+;
186 $catalog{rowtype_oid_clause
} = " rowtype_oid $+{rowtype_oid}";
190 $catalog{rowtype_oid
} = '';
191 $catalog{rowtype_oid_clause
} = '';
192 $catalog{rowtype_oid_macro
} = '';
194 $catalog{schema_macro
} = /BKI_SCHEMA_MACRO/ ?
1 : 0;
195 $declaring_attributes = 1;
197 elsif ($is_client_code)
205 push @
{ $catalog{client_code
} }, $_;
208 elsif ($declaring_attributes)
213 $declaring_attributes = 0;
218 my @attopts = split /\s+/, $_;
219 my $atttype = shift @attopts;
220 my $attname = shift @attopts;
221 die "parse error ($input_file)"
222 unless ($attname and $atttype);
224 if (exists $RENAME_ATTTYPE{$atttype})
226 $atttype = $RENAME_ATTTYPE{$atttype};
229 # If the C name ends with '[]' or '[digits]', we have
230 # an array type, so we discard that from the name and
231 # prepend '_' to the type.
232 if ($attname =~ /(\w+)\[\d*\]/)
235 $atttype = '_' . $atttype;
238 $column{type
} = $atttype;
239 $column{name
} = $attname;
240 $column{is_varlen
} = 1 if $is_varlen;
242 foreach my $attopt (@attopts)
244 if ($attopt eq 'BKI_FORCE_NULL')
246 $column{forcenull
} = 1;
248 elsif ($attopt eq 'BKI_FORCE_NOT_NULL')
250 $column{forcenotnull
} = 1;
253 # We use quotes for values like \0 and \054, to
254 # make sure all compilers and syntax highlighters
255 # can recognize them properly.
256 elsif ($attopt =~ /BKI_DEFAULT\(['"]?([^'"]+)['"]?\)/)
258 $column{default} = $1;
261 $attopt =~ /BKI_ARRAY_DEFAULT\(['"]?([^'"]+)['"]?\)/)
263 $column{array_default
} = $1;
265 elsif ($attopt =~ /BKI_LOOKUP(_OPT)?\((\w+)\)/)
267 $column{lookup
} = $2;
268 $column{lookup_opt
} = $1 ?
1 : 0;
269 # BKI_LOOKUP implicitly makes an FK reference
270 push @
{ $catalog{foreign_keys
} },
273 $atttype eq 'oidvector' || $atttype eq '_oid')
276 is_opt
=> $column{lookup_opt
},
278 pk_table
=> $column{lookup
},
285 "unknown or misformatted column option $attopt on column $attname";
288 if ($column{forcenull
} and $column{forcenotnull
})
290 die "$attname is forced both null and not null";
293 push @
{ $catalog{columns
} }, \
%column;
301 # Parses a file containing Perl data structure literals, returning live data.
303 # The parameter $preserve_comments needs to be set for callers that want
304 # to work with non-data lines in the data files, such as comments and blank
305 # lines. If a caller just wants to consume the data, leave it unset.
306 # (When requested, non-data lines will be returned as array entries that
307 # are strings not hashes, so extra code is needed to deal with that.)
310 my ($input_file, $schema, $preserve_comments) = @_;
312 open(my $ifd, '<', $input_file) || die "$input_file: $!";
313 $input_file =~ /(\w+)\.dat$/
314 or die "Input file $input_file needs to be a .dat file.\n";
318 # Scan the input file.
325 # Capture the hash ref
326 # NB: Assumes that the next hash ref can't start on the
327 # same line where the present one ended.
328 # Not foolproof, but we shouldn't need a full parser,
329 # since we expect relatively well-behaved input.
331 # Quick hack to detect when we have a full hash ref to
332 # parse. We can't just use a regex because of values in
333 # pg_aggregate and pg_proc like '{0,0}'. This will need
334 # work if we ever need to allow unbalanced braces within
341 # We're treating the input line as a piece of Perl, so we
342 # need to use string eval here. Tell perlcritic we know what
344 eval "\$hash_ref = $_"; ## no critic (ProhibitStringyEval)
347 die "$input_file: error parsing line $.:\n$_\n";
350 # Annotate each hash with the source line number.
351 $hash_ref->{line_number
} = $.;
353 # Expand tuples to their full representation.
354 AddDefaultValues
($hash_ref, $schema, $catname);
358 my $next_line = <$ifd>;
359 die "$input_file: file ends within Perl hash\n"
360 if !defined $next_line;
366 # If we found a hash reference, keep it, unless it is marked as
367 # autogenerated; in that case it'd duplicate an entry we'll
368 # autogenerate below. (This makes it safe for reformat_dat_file.pl
369 # with --full-tuples to print autogenerated entries, which seems like
370 # useful behavior for debugging.)
372 # Otherwise, we have a non-data string, which we keep only if
373 # the caller requested it.
374 if (defined $hash_ref)
376 push @
$data, $hash_ref if !$hash_ref->{autogenerated
};
380 push @
$data, $_ if $preserve_comments;
386 # If this is pg_type, auto-generate array types too.
387 GenerateArrayTypes
($schema, $data) if $catname eq 'pg_type';
392 # Fill in default values of a record using the given schema.
393 # It's the caller's responsibility to specify other values beforehand.
396 my ($row, $schema, $catname) = @_;
399 # Compute special-case column values.
400 # Note: If you add new cases here, you must also teach
401 # strip_default_values() in include/catalog/reformat_dat_file.pl
403 if ($catname eq 'pg_proc')
405 # pg_proc.pronargs can be derived from proargtypes.
406 if (defined $row->{proargtypes
})
408 my @proargtypes = split /\s+/, $row->{proargtypes
};
409 $row->{pronargs
} = scalar(@proargtypes);
413 # Now fill in defaults, and note any columns that remain undefined.
414 foreach my $column (@
$schema)
416 my $attname = $column->{name
};
418 # No work if field already has a value.
419 next if defined $row->{$attname};
421 # Ignore 'oid' columns, they're handled elsewhere.
422 next if $attname eq 'oid';
424 # If column has a default value, fill that in.
425 if (defined $column->{default})
427 $row->{$attname} = $column->{default};
431 # Failed to find a value for this field.
432 push @missing_fields, $attname;
435 # Failure to provide all columns is a hard error.
438 die sprintf "missing values for field(s) %s in %s.dat line %s\n",
439 join(', ', @missing_fields), $catname, $row->{line_number
};
443 # If a pg_type entry has an array_type_oid metadata field,
444 # auto-generate an entry for its array type.
445 sub GenerateArrayTypes
447 my $pgtype_schema = shift;
451 foreach my $elem_type (@
$types)
453 next if !(ref $elem_type eq 'HASH');
454 next if !defined($elem_type->{array_type_oid
});
458 # Set up metadata fields for array type.
459 $array_type{oid
} = $elem_type->{array_type_oid
};
460 $array_type{autogenerated
} = 1;
461 $array_type{line_number
} = $elem_type->{line_number
};
463 # Set up column values derived from the element type.
464 $array_type{typname
} = '_' . $elem_type->{typname
};
465 $array_type{typelem
} = $elem_type->{typname
};
467 # Arrays require INT alignment, unless the element type requires
469 $array_type{typalign
} = $elem_type->{typalign
} eq 'd' ?
'd' : 'i';
471 # Fill in the rest of the array entry's fields.
472 foreach my $column (@
$pgtype_schema)
474 my $attname = $column->{name
};
476 # Skip if we already set it above.
477 next if defined $array_type{$attname};
479 # Apply the BKI_ARRAY_DEFAULT setting if there is one,
480 # otherwise copy the field from the element type.
481 if (defined $column->{array_default
})
483 $array_type{$attname} = $column->{array_default
};
487 $array_type{$attname} = $elem_type->{$attname};
491 # Lastly, cross-link the array to the element type.
492 $elem_type->{typarray
} = $array_type{typname
};
494 push @array_types, \
%array_type;
497 push @
$types, @array_types;
502 # Rename temporary files to final names.
503 # Call this function with the final file name and the .tmp extension.
505 # If the final file already exists and has identical contents, don't
506 # overwrite it; this behavior avoids unnecessary recompiles due to
507 # updating the mod date on unchanged header files.
509 # Note: recommended extension is ".tmp$$", so that parallel make steps
510 # can't use the same temp files.
513 my $final_name = shift;
514 my $extension = shift;
515 my $temp_name = $final_name . $extension;
518 && compare
($temp_name, $final_name) == 0)
520 unlink($temp_name) || die "unlink: $temp_name: $!";
524 rename($temp_name, $final_name) || die "rename: $temp_name: $!";
529 # Find a symbol defined in a particular header file and extract the value.
530 # include_path should be the path to src/include/.
531 sub FindDefinedSymbol
533 my ($catalog_header, $include_path, $symbol) = @_;
536 # Make sure include path ends in a slash.
537 if (substr($include_path, -1) ne '/')
539 $include_path .= '/';
541 my $file = $include_path . $catalog_header;
542 open(my $find_defined_symbol, '<', $file) || die "$file: $!";
543 while (<$find_defined_symbol>)
545 if (/^#define\s+\Q$symbol\E\s+(\S+)/)
551 close $find_defined_symbol;
552 return $value if defined $value;
553 die "$file: no definition found for $symbol\n";
556 # Similar to FindDefinedSymbol, but looks in the bootstrap metadata.
557 sub FindDefinedSymbolFromData
559 my ($data, $symbol) = @_;
560 foreach my $row (@
{$data})
562 if ($row->{oid_symbol
} eq $symbol)
567 die "no definition found for $symbol\n";
570 # Extract an array of all the OIDs assigned in the specified catalog headers
571 # and their associated data files (if any).
572 # Caution: genbki.pl contains equivalent logic; change it too if you need to
574 sub FindAllOidsFromHeaders
576 my @input_files = @_;
580 foreach my $header (@input_files)
582 $header =~ /(.+)\.h$/
583 or die "Input files need to be header files.\n";
584 my $datfile = "$1.dat";
586 my $catalog = Catalog
::ParseHeader
($header);
588 # We ignore the pg_class OID and rowtype OID of bootstrap catalogs,
589 # as those are expected to appear in the initial data for pg_class
590 # and pg_type. For regular catalogs, include these OIDs.
591 if (!$catalog->{bootstrap
})
593 push @oids, $catalog->{relation_oid
}
594 if ($catalog->{relation_oid
});
595 push @oids, $catalog->{rowtype_oid
} if ($catalog->{rowtype_oid
});
598 # Not all catalogs have a data file.
602 Catalog
::ParseData
($datfile, $catalog->{columns
}, 0);
604 foreach my $row (@
$catdata)
606 push @oids, $row->{oid
} if defined $row->{oid
};
610 foreach my $toast (@
{ $catalog->{toasting
} })
612 push @oids, $toast->{toast_oid
}, $toast->{toast_index_oid
};
614 foreach my $index (@
{ $catalog->{indexing
} })
616 push @oids, $index->{index_oid
};
618 foreach my $other (@
{ $catalog->{other_oids
} })
620 push @oids, $other->{other_oid
};