My idea is to store the information in guc_tables.c in a .dat file similar to the catalog data in src/include/catalog/, and generated guc_tables.c from that. I want to make it easier to edit that information, and I want to be able to make changes to the downstream data structures more easily. (Essentially, those are the same reasons as for the original adoption of the .dat format.)

An important project is to adapt the GUC system to a multithreaded server. The leading idea is to convert most global variables to thread-local storage. But this doesn't work with the current global structs, because they can't contain a pointer to a thread-local variable. Some workarounds and changes exist in WIP threading branches, but they all require at least some mechanical changes to the global tables. If these tables could be generated automatically, it would be easier to try out different things.

More generally, maybe the current format of a global struct that points to many global variables is not the right one anymore. Maybe the global variables should be packaged into a struct, or several structs. Or maybe the whole thing could just be a hash table and you retrieve values when you need them. Who knows. But this would make it easier to experiment and make changes.

Another possible benefit would be that we could generate the postgresql.conf.sample file. This is also very tedious to edit and maintain, and sometimes people want to make larger changes, and this is very difficult. And then we might not need tests like 003_check_guc.pl that check the consistency of the sample file.

So here is an initial POC patch. I have written a script to convert a new src/backend/utils/misc/guc_parameters.dat to what would be guc_tables.c, but in the patch it's guc_tables_new.c. The guc_parameters.dat in the patch is populated only with a few entries that cover most of the different types and variants and possible settings, so we can see what it would look like. Eventually, this would require a big conversion.

My next goal would be to make this work so that most of guc_tables.c is generated, and nothing else changes beyond that. But before I do all the remaining tedious work, I wanted to check what people think.
From 31e00a9282e154c62197e1bbceeb328e0e088110 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Mon, 11 Aug 2025 07:57:02 +0200
Subject: [PATCH v0] WIP: Generate GUC tables from .dat file

---
 src/backend/utils/misc/gen_guc_tables.pl  | 79 +++++++++++++++++++++++
 src/backend/utils/misc/guc_parameters.dat | 75 +++++++++++++++++++++
 src/backend/utils/misc/meson.build        | 11 ++++
 3 files changed, 165 insertions(+)
 create mode 100644 src/backend/utils/misc/gen_guc_tables.pl
 create mode 100644 src/backend/utils/misc/guc_parameters.dat

diff --git a/src/backend/utils/misc/gen_guc_tables.pl 
b/src/backend/utils/misc/gen_guc_tables.pl
new file mode 100644
index 00000000000..9bd1c24ac50
--- /dev/null
+++ b/src/backend/utils/misc/gen_guc_tables.pl
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings FATAL => 'all';
+
+use FindBin;
+use lib "$FindBin::RealBin/../../catalog";
+use Catalog;
+
+# debug
+use Data::Dumper;
+
+my $input_fname = $ARGV[0];
+my $output_fname = $ARGV[1];
+
+open my $ifh, '<', $input_fname or die;
+open my $ofh, '>', $output_fname or die;
+
+my $parse = Catalog::ParseData($input_fname);
+
+sub escape
+{
+       my ($s) = @_;
+
+       $s =~ s/"/\\"/g;
+
+       return $s;
+}
+
+sub print_one_table
+{
+       my ($type) = @_;
+       my $Type = ucfirst $type;
+
+       print $ofh "struct config_${type} ConfigureNames${Type}[] =\n"
+         . "{\n";
+
+       foreach my $entry (@{$parse})
+       {
+               next if $entry->{type} ne $type;
+
+               print $ofh "#ifdef " . $entry->{ifdef} . "\n" if 
$entry->{ifdef};
+               print $ofh "\t{\n";
+               print $ofh "\t\t{\n";
+               print $ofh qq{\t\t\t"} . $entry->{name} . qq{",\n};
+               print $ofh qq{\t\t\t} . $entry->{context} . qq{,\n};
+               print $ofh qq{\t\t\t} . $entry->{group} . qq{,\n};
+               print $ofh qq{\t\t\tgettext_noop("} . 
escape($entry->{short_desc}) . qq{"),\n};
+               if ($entry->{long_desc})
+               {
+                       print $ofh qq{\t\t\tgettext_noop("} . 
escape($entry->{long_desc}) . qq{"),\n};
+               }
+               else
+               {
+                       print $ofh qq{\t\t\tNULL,\n};
+               }
+               print $ofh qq{\t\t\t} . $entry->{flags} . qq{,\n} if 
$entry->{flags};
+               print $ofh "\t\t},\n";
+               print $ofh qq{\t\t&} . $entry->{variable} . qq {,\n};
+               print $ofh qq{\t\t} . $entry->{boot_val} . qq {,\n};
+               print $ofh qq{\t\t} . $entry->{min} . qq {,\n} if 
$entry->{type} eq 'int' || $entry->{type} eq 'real';
+               print $ofh qq{\t\t} . $entry->{max} . qq {,\n} if 
$entry->{type} eq 'int' || $entry->{type} eq 'real';
+               print $ofh qq{\t\t} . $entry->{options} . qq {,\n} if 
$entry->{type} eq 'enum';
+               print $ofh qq{\t\t} . ($entry->{check_hook} || 'NULL') . qq 
{,\n};
+               print $ofh qq{\t\t} . ($entry->{assign_hook} || 'NULL') . qq 
{,\n};
+               print $ofh qq{\t\t} . ($entry->{show_hook} || 'NULL') . qq 
{,\n};
+               print $ofh "\t},\n";
+               print $ofh "#endif\n" if $entry->{ifdef};
+       }
+
+       print $ofh "\t/* End-of-list marker */\n"
+         . "\t{0}\n"
+         ."};\n\n";
+}
+
+foreach my $type (qw(bool int real string enum))
+{
+       print_one_table($type);
+}
diff --git a/src/backend/utils/misc/guc_parameters.dat 
b/src/backend/utils/misc/guc_parameters.dat
new file mode 100644
index 00000000000..beba6c9d05a
--- /dev/null
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -0,0 +1,75 @@
+[
+
+{ name => 'enable_seqscan', type => 'bool', context => 'PGC_USERSET', group => 
'QUERY_TUNING_METHOD',
+  short_desc => 'Enables the planner\'s use of sequential-scan plans.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'enable_seqscan',
+  boot_val => 'true',
+},
+
+{ name => 'enable_indexscan', type => 'bool', context => 'PGC_USERSET', group 
=> 'QUERY_TUNING_METHOD',
+  short_desc => 'Enables the planner\'s use of index-scan plans.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'enable_indexscan',
+  boot_val => 'true',
+},
+
+{ name => 'enable_partition_pruning', type => 'bool', context => 
'PGC_USERSET', group => 'QUERY_TUNING_METHOD',
+  short_desc => 'Enables plan-time and execution-time partition pruning.',
+  long_desc => 'Allows the query planner and executor to compare partition 
bounds to conditions in the query to determine which partitions must be 
scanned.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'enable_partition_pruning',
+  boot_val => 'true',
+},
+
+{ name => 'ssl', type => 'bool', context => 'PGC_SIGHUP', group => 
'CONN_AUTH_SSL',
+  short_desc => 'Enables SSL connections.',
+  variable => 'EnableSSL',
+  boot_val => 'false',
+  check_hook => 'check_ssl',
+},
+
+# this is undocumented because not exposed in a standard build
+{ name => 'optimize_bounded_sort', type => 'bool', context => 'PGC_USERSET', 
group => 'QUERY_TUNING_METHOD',
+  short_desc => 'Enables bounded sorting using heap sort.',
+  flags => 'GUC_NOT_IN_SAMPLE | GUC_EXPLAIN',
+  variable => 'optimize_bounded_sort',
+  boot_val => 'true',
+  ifdef => 'DEBUG_BOUNDED_SORT',
+},
+
+{ name => 'wal_receiver_timeout', type => 'int', context => 'PGC_SIGHUP', 
group => 'REPLICATION_STANDBY',
+  short_desc => 'Sets the maximum wait time to receive data from the sending 
server.',
+  long_desc => '0 disables the timeout.',
+  flags => 'GUC_UNIT_MS',
+  variable => 'wal_receiver_timeout',
+  boot_val => '60 * 1000',
+  min => '0',
+  max => 'INT_MAX',
+},
+
+{ name => 'seq_page_cost', type => 'real', context => 'PGC_USERSET', group => 
'QUERY_TUNING_COST',
+  short_desc => 'Sets the planner\'s estimate of the cost of a sequentially 
fetched disk page.',
+  flags => 'GUC_EXPLAIN',
+  variable => 'seq_page_cost',
+  boot_val => 'DEFAULT_SEQ_PAGE_COST',
+  min => '0',
+  max => 'DBL_MAX',
+},
+
+{ name => 'archive_command', type => 'string', context => 'PGC_SIGHUP', group 
=> 'WAL_ARCHIVING',
+  short_desc => 'Sets the shell command that will be called to archive a WAL 
file.',
+  long_desc => 'An empty string means use "archive_library".',
+  variable => 'XLogArchiveCommand',
+  boot_val => '""',
+  show_hook => 'show_archive_command',
+},
+
+{ name => 'bytea_output', type => 'enum', context => 'PGC_USERSET', group => 
'CLIENT_CONN_STATEMENT',
+  short_desc => 'Sets the output format for bytea.',
+  variable => 'bytea_output',
+  boot_val => 'BYTEA_OUTPUT_HEX',
+  options => 'bytea_output_options',
+},
+
+]
diff --git a/src/backend/utils/misc/meson.build 
b/src/backend/utils/misc/meson.build
index 9e389a00d05..a34512ec9c1 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -37,3 +37,14 @@ backend_link_with += static_library('guc-file',
 install_data('postgresql.conf.sample',
   install_dir: dir_data,
 )
+
+
+# WIP
+custom_target(
+  'guc_tables_new.c',
+  input: files('guc_parameters.dat'),
+  output: ['guc_tables_new.c'],
+  depend_files: catalog_pm,
+  command: [perl, files('gen_guc_tables.pl'), '@INPUT@', '@OUTPUT@'],
+  install: false, # for now
+)
-- 
2.50.1

Reply via email to