1#!/usr/bin/perl
2#----------------------------------------------------------------------
3#
4# renumber_oids.pl
5#    Perl script that shifts a range of OIDs in the Postgres catalog data
6#    to a different range, skipping any OIDs that are already in use.
7#
8#    Note: This does not reformat the .dat files, so you may want
9#    to run reformat_dat_file.pl afterwards.
10#
11# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
12# Portions Copyright (c) 1994, Regents of the University of California
13#
14# src/include/catalog/renumber_oids.pl
15#
16#----------------------------------------------------------------------
17
18use strict;
19use warnings;
20
21use FindBin;
22use Getopt::Long;
23
24# Must run in src/include/catalog
25chdir $FindBin::RealBin or die "could not cd to $FindBin::RealBin: $!\n";
26
27use lib "$FindBin::RealBin/../../backend/catalog/";
28use Catalog;
29
30# We'll need this number.
31my $FirstGenbkiObjectId =
32  Catalog::FindDefinedSymbol('access/transam.h', '..', 'FirstGenbkiObjectId');
33
34# Process command line switches.
35my $output_path      = '';
36my $first_mapped_oid = 0;
37my $last_mapped_oid  = $FirstGenbkiObjectId - 1;
38my $target_oid       = 0;
39
40GetOptions(
41	'output=s'           => \$output_path,
42	'first-mapped-oid=i' => \$first_mapped_oid,
43	'last-mapped-oid=i'  => \$last_mapped_oid,
44	'target-oid=i'       => \$target_oid) || usage();
45
46# Sanity check arguments.
47die "Unexpected non-switch arguments.\n" if @ARGV;
48die "--first-mapped-oid must be specified.\n"
49  if $first_mapped_oid <= 0;
50die "Empty mapped OID range.\n"
51  if $last_mapped_oid < $first_mapped_oid;
52die "--target-oid must be specified.\n"
53  if $target_oid <= 0;
54die "--target-oid must not be within mapped OID range.\n"
55  if $target_oid >= $first_mapped_oid && $target_oid <= $last_mapped_oid;
56
57# Make sure output_path ends in a slash.
58if ($output_path ne '' && substr($output_path, -1) ne '/')
59{
60	$output_path .= '/';
61}
62
63# Collect all the existing assigned OIDs (including those to be remapped).
64my @header_files = (glob("pg_*.h"), qw(indexing.h toasting.h));
65my $oids = Catalog::FindAllOidsFromHeaders(@header_files);
66
67# Hash-ify the existing OIDs for convenient lookup.
68my %oidhash;
69@oidhash{@$oids} = undef;
70
71# Select new OIDs for existing OIDs in the mapped range.
72# We do this first so that we preserve the ordering of the mapped OIDs
73# (for reproducibility's sake), and so that if we fail due to running out
74# of OID room, that happens before we've overwritten any files.
75my %maphash;
76my $next_oid = $target_oid;
77
78for (
79	my $mapped_oid = $first_mapped_oid;
80	$mapped_oid <= $last_mapped_oid;
81	$mapped_oid++)
82{
83	next if !exists $oidhash{$mapped_oid};
84	$next_oid++
85	  while (
86		exists $oidhash{$next_oid}
87		|| (   $next_oid >= $first_mapped_oid
88			&& $next_oid <= $last_mapped_oid));
89	die "Reached FirstGenbkiObjectId before assigning all OIDs.\n"
90	  if $next_oid >= $FirstGenbkiObjectId;
91	$maphash{$mapped_oid} = $next_oid;
92	$next_oid++;
93}
94
95die "There are no OIDs in the mapped range.\n" if $next_oid == $target_oid;
96
97# Read each .h file and write out modified data.
98foreach my $input_file (@header_files)
99{
100	$input_file =~ /(\w+)\.h$/
101	  or die "Input file $input_file needs to be a .h file.\n";
102	my $catname = $1;
103
104	# Ignore generated *_d.h files.
105	next if $catname =~ /_d$/;
106
107	open(my $ifd, '<', $input_file) || die "$input_file: $!";
108
109	# Write output files to specified directory.
110	# Use a .tmp suffix, then rename into place, in case we're overwriting.
111	my $output_file     = "$output_path$catname.h";
112	my $tmp_output_file = "$output_file.tmp";
113	open my $ofd, '>', $tmp_output_file
114	  or die "can't open $tmp_output_file: $!";
115	my $changed = 0;
116
117	# Scan the input file.
118	while (<$ifd>)
119	{
120		my $line = $_;
121
122		# Check for OID-defining macros that Catalog::ParseHeader knows about,
123		# and update OIDs as needed.
124		if ($line =~ m/^(DECLARE_TOAST\(\s*\w+,\s*)(\d+)(,\s*)(\d+)\)/)
125		{
126			my $oid2 = $2;
127			my $oid4 = $4;
128			if (exists $maphash{$oid2})
129			{
130				$oid2 = $maphash{$oid2};
131				my $repl = $1 . $oid2 . $3 . $oid4 . ")";
132				$line =~ s/^DECLARE_TOAST\(\s*\w+,\s*\d+,\s*\d+\)/$repl/;
133				$changed = 1;
134			}
135			if (exists $maphash{$oid4})
136			{
137				$oid4 = $maphash{$oid4};
138				my $repl = $1 . $oid2 . $3 . $oid4 . ")";
139				$line =~ s/^DECLARE_TOAST\(\s*\w+,\s*\d+,\s*\d+\)/$repl/;
140				$changed = 1;
141			}
142		}
143		elsif (
144			$line =~ m/^(DECLARE_(UNIQUE_)?INDEX\(\s*\w+,\s*)(\d+)(,\s*.+)\)/)
145		{
146			if (exists $maphash{$3})
147			{
148				my $repl = $1 . $maphash{$3} . $4 . ")";
149				$line =~
150				  s/^DECLARE_(UNIQUE_)?INDEX\(\s*\w+,\s*\d+,\s*.+\)/$repl/;
151				$changed = 1;
152			}
153		}
154		elsif ($line =~ m/^CATALOG\((\w+),(\d+),(\w+)\)/)
155		{
156			if (exists $maphash{$2})
157			{
158				my $repl =
159				  "CATALOG(" . $1 . "," . $maphash{$2} . "," . $3 . ")";
160				$line =~ s/^CATALOG\(\w+,\d+,\w+\)/$repl/;
161				$changed = 1;
162			}
163
164			if ($line =~ m/BKI_ROWTYPE_OID\((\d+),(\w+)\)/)
165			{
166				if (exists $maphash{$1})
167				{
168					my $repl =
169					  "BKI_ROWTYPE_OID(" . $maphash{$1} . "," . $2 . ")";
170					$line =~ s/BKI_ROWTYPE_OID\(\d+,\w+\)/$repl/;
171					$changed = 1;
172				}
173			}
174		}
175
176		# In indexing.h and toasting.h only, check for #define SYM nnnn,
177		# and replace if within mapped range.
178		elsif ($line =~ m/^(\s*#\s*define\s+\w+\s+)(\d+)\b/)
179		{
180			if (($catname eq 'indexing' || $catname eq 'toasting')
181				&& exists $maphash{$2})
182			{
183				my $repl = $1 . $maphash{$2};
184				$line =~ s/^\s*#\s*define\s+\w+\s+\d+\b/$repl/;
185				$changed = 1;
186			}
187		}
188
189		print $ofd $line;
190	}
191
192	close $ifd;
193	close $ofd;
194
195	# Avoid updating files if we didn't change them.
196	if ($changed || $output_path ne '')
197	{
198		rename $tmp_output_file, $output_file
199		  or die "can't rename $tmp_output_file to $output_file: $!";
200	}
201	else
202	{
203		unlink $tmp_output_file
204		  or die "can't unlink $tmp_output_file: $!";
205	}
206}
207
208# Likewise, read each .dat file and write out modified data.
209foreach my $input_file (glob("pg_*.dat"))
210{
211	$input_file =~ /(\w+)\.dat$/
212	  or die "Input file $input_file needs to be a .dat file.\n";
213	my $catname = $1;
214
215	open(my $ifd, '<', $input_file) || die "$input_file: $!";
216
217	# Write output files to specified directory.
218	# Use a .tmp suffix, then rename into place, in case we're overwriting.
219	my $output_file     = "$output_path$catname.dat";
220	my $tmp_output_file = "$output_file.tmp";
221	open my $ofd, '>', $tmp_output_file
222	  or die "can't open $tmp_output_file: $!";
223	my $changed = 0;
224
225	# Scan the input file.
226	while (<$ifd>)
227	{
228		my $line = $_;
229
230		# Check for oid => 'nnnn', and replace if within mapped range.
231		if ($line =~ m/\b(oid\s*=>\s*)'(\d+)'/)
232		{
233			if (exists $maphash{$2})
234			{
235				my $repl = $1 . "'" . $maphash{$2} . "'";
236				$line =~ s/\boid\s*=>\s*'\d+'/$repl/;
237				$changed = 1;
238			}
239		}
240
241		# Likewise for array_type_oid.
242		if ($line =~ m/\b(array_type_oid\s*=>\s*)'(\d+)'/)
243		{
244			if (exists $maphash{$2})
245			{
246				my $repl = $1 . "'" . $maphash{$2} . "'";
247				$line =~ s/\barray_type_oid\s*=>\s*'\d+'/$repl/;
248				$changed = 1;
249			}
250		}
251
252		print $ofd $line;
253	}
254
255	close $ifd;
256	close $ofd;
257
258	# Avoid updating files if we didn't change them.
259	if ($changed || $output_path ne '')
260	{
261		rename $tmp_output_file, $output_file
262		  or die "can't rename $tmp_output_file to $output_file: $!";
263	}
264	else
265	{
266		unlink $tmp_output_file
267		  or die "can't unlink $tmp_output_file: $!";
268	}
269}
270
271sub usage
272{
273	my $last = $FirstGenbkiObjectId - 1;
274	die <<EOM;
275Usage: renumber_oids.pl [--output PATH] --first-mapped-oid X [--last-mapped-oid Y] --target-oid Z
276
277Options:
278    --output PATH           output directory (default '.')
279    --first-mapped-oid X    first OID to be moved
280    --last-mapped-oid Y     last OID to be moved (default $last)
281    --target-oid Z          first OID to move to
282
283Catalog *.h and *.dat files are updated and written to the
284output directory; by default, this overwrites the input files.
285
286Caution: the output PATH will be interpreted relative to
287src/include/catalog, even if you start the script
288in some other directory.
289
290EOM
291}
292