From 4385545514099eb94c1a624d2a082e544ca9c6bc Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horikyota.ntt@gmail.com>
Date: Thu, 16 Jun 2022 17:00:14 +0900
Subject: [PATCH] Add fileval-bootval consistency check of GUC parameters

We should keep GUC variables consistent between the default values
written in postgresql.conf.sample and defined in guc.c. Add an
automated way to check for the consistency to the TAP test suite.
For simplicity we don't check values that require unit conversion.
Some variables are still excluded since they cannot be checked simple
way.
---
 src/test/modules/test_misc/t/003_check_guc.pl | 76 +++++++++++++++++--
 1 file changed, 70 insertions(+), 6 deletions(-)

diff --git a/src/test/modules/test_misc/t/003_check_guc.pl b/src/test/modules/test_misc/t/003_check_guc.pl
index 60459ef759..a3334fad76 100644
--- a/src/test/modules/test_misc/t/003_check_guc.pl
+++ b/src/test/modules/test_misc/t/003_check_guc.pl
@@ -11,18 +11,39 @@ my $node = PostgreSQL::Test::Cluster->new('main');
 $node->init;
 $node->start;
 
+# parameter names that cannot get consistency check performed
+my @ignored_parameters = (
+	'data_directory',
+	'hba_file',
+	'ident_file',
+	'krb_server_keyfile',
+	'timezone_abbreviations',
+	'lc_messages',
+	'log_file_mode',
+	'unix_socket_permissions',
+	'wal_sync_method'
+  );
+
 # Grab the names of all the parameters that can be listed in the
 # configuration sample file.  config_file is an exception, it is not
 # in postgresql.conf.sample but is part of the lists from guc.c.
 my $all_params = $node->safe_psql(
 	'postgres',
-	"SELECT name
+	"SELECT lower(name), vartype, unit, boot_val, '!'
      FROM pg_settings
    WHERE NOT 'NOT_IN_SAMPLE' = ANY (pg_settings_get_flags(name)) AND
        name <> 'config_file'
      ORDER BY 1");
 # Note the lower-case conversion, for consistency.
-my @all_params_array = split("\n", lc($all_params));
+my %all_params_hash;
+foreach my $line (split("\n", $all_params))
+{
+	my @f = split('\|', $line);
+	fail("query returned wrong number of columns: $#f : $line") if ($#f != 4);
+	$all_params_hash{$f[0]}->{type} = $f[1];
+	$all_params_hash{$f[0]}->{unit} = $f[2];
+	$all_params_hash{$f[0]}->{bootval} = $f[3];
+}
 
 # Grab the names of all parameters marked as NOT_IN_SAMPLE.
 my $not_in_sample = $node->safe_psql(
@@ -43,7 +64,7 @@ my @gucs_in_file;
 
 # Read the sample file line-by-line, checking its contents to build a list
 # of everything known as a GUC.
-my $num_tests = 0;
+my @check_elems = ();
 open(my $contents, '<', $sample_file)
   || die "Could not open $sample_file: $!";
 while (my $line = <$contents>)
@@ -53,11 +74,16 @@ while (my $line = <$contents>)
 	# file.
 	# - Valid configuration options are followed immediately by " = ",
 	# with one space before and after the equal sign.
-	if ($line =~ m/^#?([_[:alpha:]]+) = .*/)
+	if ($line =~ m/^#?([_[:alpha:]]+) = (.*)$/)
 	{
 		# Lower-case conversion matters for some of the GUCs.
 		my $param_name = lc($1);
 
+		# extract value
+		my $file_value = $2;
+		$file_value =~ s/\s*#.*$//;		# strip trailing comment
+		$file_value =~ s/^'(.*)'$/$1/;	# strip quotes
+
 		# Ignore some exceptions.
 		next if $param_name eq "include";
 		next if $param_name eq "include_dir";
@@ -66,19 +92,57 @@ while (my $line = <$contents>)
 		# Update the list of GUCs found in the sample file, for the
 		# follow-up tests.
 		push @gucs_in_file, $param_name;
+
+		# Check for consistency between bootval and file value.
+		if (!grep { $_ eq $param_name } @ignored_parameters)
+		{
+			push (@check_elems, "('$param_name','$file_value')");
+		}
 	}
 }
 
 close $contents;
 
+# Run consistency check between config-file's default value and boot
+# values.  For now we check only variables in string and integers
+# without unit.
+my $check_query =
+  'SELECT f.n, f.v, s.boot_val FROM (VALUES '.
+  join(',', @check_elems).
+  ') f(n,v) LEFT JOIN pg_settings s ON lower(s.name) = f.n '.
+  "WHERE (lower(f.v) <> COALESCE(lower(s.boot_val), '') ".
+  "       AND (s.vartype = 'string' OR s.vartype = 'enum'))".
+  'OR s.name IS NULL';
+
+is ($node->safe_psql('postgres', $check_query), '',
+	'check if fileval-bootval consistency is fine for string variables');
+
+$check_query =
+  'SELECT f.n, f.v, s.boot_val FROM (VALUES '.
+  join(',', @check_elems).
+  ') f(n,v) LEFT JOIN pg_settings s ON lower(s.name) = f.n '.
+  "WHERE ((s.vartype = 'integer' AND s.unit IS NULL) AND f.v <> s.boot_val) ".
+  'OR s.name IS NULL';
+is ($node->safe_psql('postgres', $check_query), '',
+	'check if fileval-bootval consistency is fine for integer variables');
+
+$check_query =
+  'SELECT f.n, f.v, s.boot_val FROM (VALUES '.
+  join(',', @check_elems).
+  ') f(n,v) LEFT JOIN pg_settings s ON lower(s.name) = f.n '.
+  "WHERE ((s.vartype = 'real' AND s.unit IS NULL) ".
+  "        AND (f.v::real <> s.boot_val::real)) ".
+  'OR s.name IS NULL';
+is ($node->safe_psql('postgres', $check_query), '',
+	'check if fileval-bootval consistency is fine for real variables');
+
 # Cross-check that all the GUCs found in the sample file match the ones
 # fetched above.  This maps the arrays to a hash, making the creation of
 # each exclude and intersection list easier.
 my %gucs_in_file_hash  = map { $_ => 1 } @gucs_in_file;
-my %all_params_hash    = map { $_ => 1 } @all_params_array;
 my %not_in_sample_hash = map { $_ => 1 } @not_in_sample_array;
 
-my @missing_from_file = grep(!$gucs_in_file_hash{$_}, @all_params_array);
+my @missing_from_file = grep(!$gucs_in_file_hash{$_}, keys %all_params_hash);
 is(scalar(@missing_from_file),
 	0, "no parameters missing from postgresql.conf.sample");
 
-- 
2.31.1

