[PATCH] Introduce unified support for composite GUC options
Hello hackers,
This patch adds a unified mechanism for declaring and using composite configuration options in GUC, eliminating the need to write a custom parser for each new complex data type. New syntax for end user is json-like.
Currently, adding a new composite configuration option requires a significant amount of boilerplate code: * For DBAs: Learning a new syntax for each composite option. * For developers: Implementing a new parser from scratch for each composite type in GUC.
This patch solves these problems by providing a declarative system for defining composite types and their structure.
Major changes:
- guc_tables.h: Added new type config_composite for all composite configuration options.
- guc_composite.c: This file contains all functions related to composite options: calculating alignments, defining field types, working with memory, serialization. The functions from here are used in guc.c and guc_composite_gram.y
- guc.c: New code in this file describes the behavior of the system in the case of PGC_COMPOSITE
- guc_composite_scan.l: guc_composite_gram.y is a lexer and parser for values of composite data types.
Usage features:
Mapping between UI representation and internal variables works due to the signature that the programmer declares for the composite type. For core options, the declaration data is in the UserDefinedConfigureTypes array. For extensions composite types are declared using the DefineCustomCompositeType function.
All declarations must be arranged topologically. That is, if the type A option contains a type B field, then type B must be declared first, and only after that type A.
The main fields in the type definition are the type name and its signature.
The type signature has the following syntax:
“field_type field_name; field_type field_name; ...; field_type field_name”
where field_type is the already registered type, field_name is the field name.
There are also data types that do not need to be declared - these are arrays. So, if there is a registered type A, then the following data types automatically become available: A[n] is a static array of length n and A[] is a dynamic array.
Note that the declared type signature must exactly match the signature of the structure in the C code, since it will then be used to calculate the alignment of fields according to the rules of the C language.
Dynamic arrays are always mapped into a structure like:
struct DynArr {
void *data; //pointer to data
int size; //length of the array
}
After declaring the type definition, you can declare a composite type configuration option. The core options are declared in the guc_parameters.dat file. They must specify the type => ‘composite’ fields and specify in the type_name field the name of the composite type that was declared earlier. In the boot_val field, write a pointer to a global variable that will store this value. Options from extensions are declared using DefineCustomCompositeVariable.
Now you can use the following syntax to work with the new options both in the configuration file and in psql:
Access field of the struct: option_name->field_name
Access to an array element: option_name[index]
You can combine these access methods.
Dynamic arrays always have implicit fields data and size. data is the data of the array, size is its length.
Values of composite types have the following syntax:
Structures: {field: value, ..., field: value}
Static arrays: [index: value, index: value]
As mentioned earlier, dynamic arrays have implicit fields, so you can use 2 syntaxes to set values.:
compact (same as for static arrays) and extended:
{data: [index: value, .., index: value], size: value}.
It is not necessary to write indexes in array values. If you write without indexes, it is assumed that indexing starts from 0 with an increment of 1. In this case, all elements within the same array must be either with or without indexes.
When using the show command, the display of the dynamic array depends on the extended_guc_arrays option. If this flag is true, then the extended form is used, otherwise the compact form is used.
String values within composite types also support escape sequences.
All the functionality available to scalar options is also supported, such as: …
The system uses incremental semantics. This means that when writing to a .conf file or the set command, only the specified fields of the structure will be changed, the remaining fields will not be involved. This semantics also applies to the ALTER SYSTEM. When using ALTER SYSTEM, the current value will be written to the .auto.conf file with the changed fields that were described when calling the command, while the current value of the option will not change.
The patch applies cleanly to the master (454c046094ab3431c2ce0c540c46e623bc05bd1a).
In the additional patch (guc_composite_types_tests.patch), I added several composite options so that the new functionality could be tested using their example. Regression and TAP tests were written for them in the same patch.
I would appreciate any feedback and review.
Best regards,
Anton Chumak
Attachments:
guc_composite_types.patchtext/x-patchDownload
From 88af54efa27108826f45eb89ac1f427fb4db2f58 Mon Sep 17 00:00:00 2001
From: Anton Chumak <A.M.Chumak@yandex.com>
Date: Mon, 8 Sep 2025 10:10:10 +0700
Subject: [PATCH] Composite data types have been added to the GUC
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The new module of the configuration system allows you to map the values of
composite data types into composite variables (structures, static arrays,
dynamic arrays) in the C code in a unified way.
Mapping works due to the signature that the programmer declares for the
composite type. For core options, the declaration data is in the
UserDefinedConfigureTypes array. Composite types for options from
extensions are declared using the DefineCustomCompositeType function.
All declarations must be arranged topologically. That is, if the type A
option contains a type B field, then type B must first be declared,
and then type A.
The main fields in the type definition are the type name and its signature.
The type signature has the following syntax:
“field_type field_name; field_type field_name; ...; field_type field_name”
where field_type is the already registered type, field_name is the field name.
There are also data types that do not need to be declared - these are arrays.
So, if there is a registered type A, then data types automatically become
available: A[n] is a static array of length n and A[] is a dynamic array.
Note that the declared type signature must match the signature of the structure
in the C code, since it will then be used to calculate the alignment of fields
according to the rules of the C language.
Dynamic arrays are always mapped into a structure like:
struct DynArr {
void *data; //pointer to data
int size; //length of the array
}
After declaring the type, you can declare a composite type configuration
option. The core options are declared in the guc_parameters.dat file. They must
specify the type => ‘composite’ fields and specify in the type_name field
the name of the composite type that was declared earlier. In the boot_val
field, write a pointer to a global variable that will store this value.
Options from extensions are declared using DefineCustomCompositeVariable.
Now you can use the following syntax to work with the new options both in
the configuration file and in psql:
Field access: option_name->field_name
Access to an array element: option_name[index]
You can combine these access methods.
Dynamic arrays always have implicit fields data and size.
data is the data of the array, size is its length.
Values of composite types have the following syntax:
- Structures: {field: value, ..., field: value}
- Static arrays: [index: value, index: value]
- Dynamic arrays arrays have implicit fields, so you can use 2 syntaxes:
-- compact (same as for static arrays) and
-- extended:
{data: [index: value, .., index: value], size: value}.
It is not necessary to write indexes in array values. If you write
without indexes, it is assumed that indexing starts from 0 with an increment
of 1. In this case, all elements within the same array must be either with
or without indexes.
When using the show command, the display of the dynamic array depends on the
extended_guc_arrays option. If this flag is true, then the extended form
is used, otherwise the compact form is used.
String values within composite types also support escape sequences.
All the functionality available to scalar options is also
The system uses incremental semantics. This means that when writing to a .conf
file or the set command, only the specified fields of the structure will be
changed, the remaining fields will not be involved. The same applies to the
alter system see. When using this command, the current value will be written
to the .auto.conf file with the changed fields that were described when
calling the command, while the current value of the option will not change.
---
src/backend/Makefile | 2 +-
src/backend/nodes/makefuncs.c | 16 +
src/backend/nodes/value.c | 14 +
src/backend/parser/gram.y | 72 +-
src/backend/parser/scan.l | 11 +-
src/backend/utils/misc/Makefile | 16 +
src/backend/utils/misc/README | 118 ++
src/backend/utils/misc/gen_guc_tables.pl | 4 +-
src/backend/utils/misc/guc-file.l | 21 +-
src/backend/utils/misc/guc.c | 1120 +++++++++++++-
src/backend/utils/misc/guc_composite.c | 1486 +++++++++++++++++++
src/backend/utils/misc/guc_composite.h | 84 ++
src/backend/utils/misc/guc_composite_gram.y | 787 ++++++++++
src/backend/utils/misc/guc_composite_scan.l | 132 ++
src/backend/utils/misc/guc_funcs.c | 113 +-
src/backend/utils/misc/guc_parameters.dat | 6 +
src/backend/utils/misc/guc_tables.c | 64 +-
src/backend/utils/misc/help_config.c | 13 +
src/backend/utils/misc/meson.build | 26 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/value.h | 10 +
src/include/utils/guc.h | 26 +
src/include/utils/guc_tables.h | 40 +
src/interfaces/ecpg/preproc/parse.pl | 4 +
24 files changed, 4108 insertions(+), 78 deletions(-)
create mode 100644 src/backend/utils/misc/guc_composite.c
create mode 100644 src/backend/utils/misc/guc_composite.h
create mode 100644 src/backend/utils/misc/guc_composite_gram.y
create mode 100644 src/backend/utils/misc/guc_composite_scan.l
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 7344c8c7f5..d270596ed4 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -166,7 +166,7 @@ generated-parser-sources:
$(MAKE) -C bootstrap bootparse.c bootparse.h bootscanner.c
$(MAKE) -C replication repl_gram.c repl_gram.h repl_scanner.c syncrep_gram.c syncrep_gram.h syncrep_scanner.c
$(MAKE) -C utils/adt jsonpath_gram.c jsonpath_gram.h jsonpath_scan.c
- $(MAKE) -C utils/misc guc-file.c
+ $(MAKE) -C utils/misc guc-file.c guc_composite_gram.c guc_composite_gram.h guc_composite_scan.c
##########################################################################
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41..4707d20749 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -626,6 +626,22 @@ makeStringConst(char *str, int location)
return (Node *) n;
}
+/*
+ * makeSerializedCompositeConst -
+ * build a A_Const node of type T_SerializedComposite for given string
+ */
+Node *
+makeSerializedCompositeConst(char *str, int location)
+{
+ A_Const *n = makeNode(A_Const);
+
+ n->val.sval.type = T_SerializedComposite;
+ n->val.sval.sval = str;
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeDefElem -
* build a DefElem node
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5a8c1ce247..e3af859900 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -81,3 +81,17 @@ makeBitString(char *str)
v->bsval = str;
return v;
}
+
+/*
+ * makeSerializedComposite
+ *
+ * Caller is responsible for passing a palloc'd string.
+ */
+SerializedComposite *
+makeSerializedComposite(char *str)
+{
+ SerializedComposite *v = makeNode(SerializedComposite);
+
+ v->sval = str;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f..48f6291323 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,9 +575,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> Iconst SignedIconst
%type <str> Sconst comment_text notify_payload
-%type <str> RoleId opt_boolean_or_string
+%type <str> RoleId opt_boolean_or_string opt_boolean_or_string_extended
%type <list> var_list
-%type <str> ColId ColLabel BareColLabel
+%type <str> ColId ColLabel BareColLabel IndexCh
+%type <str> composite_only list_expr list_item
%type <str> NonReservedWord NonReservedWord_or_Sconst
%type <str> var_name type_function_name param_name
%type <str> createdb_opt_name plassign_target
@@ -684,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* DOT_DOT is unused in the core SQL grammar, and so will always provoke
* parse errors. It is needed by PL/pgSQL.
*/
-%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
+%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST DEREF Op
%token <ival> ICONST PARAM
%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
@@ -889,6 +890,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%left AT /* sets precedence for AT TIME ZONE, AT LOCAL */
%left COLLATE
%right UMINUS
+%left '{' '}' /* that is '{' and '}' */
%left '[' ']'
%left '(' ')'
%left TYPECAST
@@ -1842,8 +1844,14 @@ set_rest_more: /* Generic SET syntaxes: */
var_name: ColId { $$ = $1; }
| var_name '.' ColId
{ $$ = psprintf("%s.%s", $1, $3); }
+ | var_name DEREF ColId
+ { $$ = psprintf("%s->%s", $1, $3); }
+ | var_name IndexCh
+ { $$ = psprintf("%s%s", $1, $2); }
;
+IndexCh: '['ICONST']' { $$ = psprintf("[%d]", $2); };
+
var_list: var_value { $$ = list_make1($1); }
| var_list ',' var_value { $$ = lappend($1, $3); }
;
@@ -1852,7 +1860,51 @@ var_value: opt_boolean_or_string
{ $$ = makeStringConst($1, @1); }
| NumericOnly
{ $$ = makeAConst($1, @1); }
- ;
+ | composite_only
+ { $$ = makeSerializedCompositeConst($1, @1); }
+ ;
+
+composite_only: '{' list_expr '}'
+ { $$ = psprintf("{%s}", $2); }
+ | '{' '}'
+ { $$ = psprintf("{}"); }
+ | '[' list_expr ']'
+ { $$ = psprintf("[%s]", $2); }
+ | '[' ']'
+ { $$ = psprintf("[]"); }
+ ;
+
+list_expr: list_item
+ { $$ = psprintf("%s", $1); }
+ | list_expr ',' list_item
+ { $$ = psprintf("%s, %s", $1, $3); }
+ ;
+
+list_item: ICONST ':' ICONST
+ { $$ = psprintf("%d: %d", $1, $3); }
+ | ICONST ':' FCONST
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | ICONST ':' opt_boolean_or_string_extended
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | ICONST ':' composite_only
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | opt_boolean_or_string ':' ICONST
+ { $$ = psprintf("%s: %d", $1, $3); }
+ | opt_boolean_or_string ':' FCONST
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | opt_boolean_or_string ':' opt_boolean_or_string_extended
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | opt_boolean_or_string ':' composite_only
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | ICONST
+ { $$ = psprintf("%d", $1); }
+ | FCONST
+ { $$ = psprintf("%s", $1); }
+ | opt_boolean_or_string_extended
+ { $$ = psprintf("%s", $1); }
+ | composite_only
+ { $$ = psprintf("%s", $1); }
+ ;
iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| READ COMMITTED { $$ = "read committed"; }
@@ -1860,6 +1912,18 @@ iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| SERIALIZABLE { $$ = "serializable"; }
;
+opt_boolean_or_string_extended:
+ TRUE_P { $$ = "true"; }
+ | FALSE_P { $$ = "false"; }
+ | ON { $$ = "on"; }
+ /*
+ * OFF is also accepted as a boolean value, but is handled by
+ * the NonReservedWord rule. The action for booleans and strings
+ * is the same, so we don't need to distinguish them here.
+ */
+ | NonReservedWord_or_Sconst { $$ = psprintf("'%s'", $1); }
+ ;
+
opt_boolean_or_string:
TRUE_P { $$ = "true"; }
| FALSE_P { $$ = "false"; }
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 08990831fe..8a7bc6533f 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -331,7 +331,7 @@ xcinside [^*/]+
ident_start [A-Za-z\200-\377_]
ident_cont [A-Za-z\200-\377_0-9\$]
-
+dereference "->"
identifier {ident_start}{ident_cont}*
/* Assorted special-case operators and operator-like tokens */
@@ -363,7 +363,7 @@ not_equals "!="
* If you change either set, adjust the character lists appearing in the
* rule for "operator"!
*/
-self [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self [,()\[\].;\:\+\-\*\/\%\^\<\>\=\{\}]
op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
operator {op_chars}+
@@ -883,6 +883,11 @@ other .
return yytext[0];
}
+{dereference} {
+ SET_YYLLOC();
+ return DEREF;
+}
+
{operator} {
/*
* Check for embedded slash-star or dash-dash; those
@@ -955,7 +960,7 @@ other .
* that the "self" rule would have.
*/
if (nchars == 1 &&
- strchr(",()[].;:+-*/%^<>=", yytext[0]))
+ strchr(",()[].;:+-*/%^<>={}", yytext[0]))
return yytext[0];
/*
* Likewise, if what we have left is two chars, and
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b362ae4377..124133e7dd 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -16,8 +16,11 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = \
conffiles.o \
+ guc_composite.o \
guc.o \
guc-file.o \
+ guc_composite_gram.o\
+ guc_composite_scan.o\
guc_funcs.o \
guc_tables.o \
help_config.o \
@@ -42,5 +45,18 @@ endif
include $(top_srcdir)/src/backend/common.mk
+# See notes in src/backend/parser/Makefile about the following two rules
+guc_composite_gram.h: guc_composite_gram.c
+ touch $@
+
+guc_composite_gram.c: BISONFLAGS += -d
+
+# Force these dependencies to be known even without dependency info built:
+guc_composite_gram.o guc_composite_scanner.o: guc_composite_gram.h
+
+
clean:
rm -f guc-file.c
+ rm -f guc_composite_scan.c
+ rm -f guc_composite_gram.c
+ rm -f guc_composite_gram.h
diff --git a/src/backend/utils/misc/README b/src/backend/utils/misc/README
index 85d97d29b6..a660d890c5 100644
--- a/src/backend/utils/misc/README
+++ b/src/backend/utils/misc/README
@@ -292,3 +292,121 @@ worry about NULL values ever, the variable can be given a non-null static
initializer as well as a non-null boot_val. guc.c will overwrite the
static initializer pointer with a copy of the boot_val during
InitializeGUCOptions, but the variable will never contain a NULL.
+
+
+GUC composite type options
+--------------------------
+
+Composite types include structures, static and dynamic arrays.
+Composite types can have several levels of nesting.
+
+User interface:
+
+ To use the composite type options, you need to register the types
+ used in the system. To do this, add the type description to the guc_tables.c
+ file in the UserDefinedConfigureTypes array or use the DefineCustomCompositeType
+ function if you are creating a type from an extension. Note that the sequence
+ of fields in the type signature must exactly match the signature
+ of the structure in the C code.
+
+ type definition is a structure type_definition. See guc_tables.h
+ Main fields of the definition are type_name and signature.
+ The signature syntax is as follows:
+ "<type_1> <name_1>;<type_2> <name_2>; ... ; <type_K> <name_K>"
+ type_i - type of field
+ name_i - name of field
+
+ The field can have a type that is already registered in the system
+ (types that are in the array before) or array type of already registered types
+ Arrays syntax:
+ <type>[<length>] - static array
+ <type>[] - dynamic array
+
+ Representation in C code of dynamic array is a structure like
+ struct DynamicArray
+ {
+ void *data;
+ int size;
+ };
+ It is important to keep the order of the fields, because mapping takes place
+ according to the type signature and alignment rules of the C language.
+
+ After registering the composite type, you can use it when declaring
+ a new composite option in the ConfigureNamesComposite array.
+
+ Composite types in the user interface support the following syntax:
+ option_name = {field_name: field_value, ..., field_name: field_value}
+ option_name->field_name = field_value
+ option_name = [value, value, ..., value]
+ option_name = [<index>: value, ..., <index>: value]
+ The same syntax is used in psql in SET and SHOW commands.
+
+Example:
+
+ Consider example option in C code:
+
+ struct DynArrStr
+ {
+ char **data;
+ int size;
+ };
+
+ struct ExampleOptionType
+ {
+ int a;
+ struct DynArrStr b;
+ double c[3];
+ };
+
+ struct ExampleOptionType example_option;
+
+ To register new option in GUC follow instruction:
+
+ 1. Declare global structure for boot value:
+
+ struct ExampleOptionType example_option_boot;
+
+ 2. In guc_tables.c in UserDefinedConfigureTypes declare element
+
+ {
+ "example_type",
+ "int a; string[] b; real[3] c"
+ } /* other fields will be filled automatically */
+ Notice, that declarations in UserDefinedConfigureTypes
+ must be sorted with topological order
+
+ 3. In guc_tables.c in ConfigureNamesComposite declare element
+
+ {
+ {"example_option",...}, /* config_generic */
+ "example_type", /* option type */
+ &example_option,
+ &example_option_boot,
+ NULL, NULL, NULL /* hooks */
+ }
+ Option type is a type that declared in UserDefinedConfigureTypes or array type
+
+ For extensions use functions:
+ DefineCustomCompositeType - define new type
+ DefineCustomCompositeVariable - define new composite guc option
+
+Internals:
+
+ Composite types use incremental value setting semantics.
+ When setting a value, you can omit some fields of the structure/array,
+ then the unspecified fields will not change their values.
+ Maintaining this semantics for extensions required special processing
+ of placeholders for structural values. Instead of rewriting the value
+ of the field, we add it using ';'. This preserves the linear order in which
+ values are applied.
+
+ Since the logic of processing placeholders for composite types differs
+ from others, there is a need to distinguish between composite types and
+ scalar ones. To do this, it is forbidden to wrap a compound type value
+ in quotation marks. Inside, the system will add characters "->" to the
+ name of such a value, which only composite option can have.
+
+ In general, the processing of quotation marks for composite types differs
+ from scalar types, since string fields are wrapped in quotation marks
+ and the usual Deescape is not suitable for the entire structure.
+ Therefore, be careful when changing this logic.
diff --git a/src/backend/utils/misc/gen_guc_tables.pl b/src/backend/utils/misc/gen_guc_tables.pl
index bc8233f2d3..a612b2ddfd 100644
--- a/src/backend/utils/misc/gen_guc_tables.pl
+++ b/src/backend/utils/misc/gen_guc_tables.pl
@@ -25,7 +25,7 @@ my $parse = Catalog::ParseData($input_fname);
open my $ofh, '>', $output_fname or die;
print_boilerplate($ofh, $output_fname, 'GUC tables');
-foreach my $type (qw(bool int real string enum))
+foreach my $type (qw(bool int real string enum composite))
{
print_one_table($ofh, $type);
}
@@ -79,6 +79,8 @@ sub print_one_table
print $ofh "\n";
}
print $ofh "\t\t},\n";
+ printf $ofh "\t\t%s,\n", dquote($entry->{type_name})
+ if $entry->{type} eq 'composite';
print $ofh "\t\t&$entry->{variable},\n";
print $ofh "\t\t$entry->{boot_val},";
print $ofh " $entry->{min},"
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index 11a1e2a3f9..3e3f3ae522 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -83,10 +83,13 @@ LETTER [A-Za-z_\200-\377]
LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
ID {LETTER}{LETTER_OR_DIGIT}*
-QUALIFIED_ID {ID}"."{ID}
+DEREF "->"
+INDEX "["{DIGIT}+"]"
+AID {ID}({DEREF}{ID}|{INDEX})*
+QUALIFIED_ID {ID}"."{AID}
-UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
STRING \'([^'\\\n]|\\.|\'\')*\'
+UNQUOTED_STRING ({LETTER}|\{|\[)({LETTER_OR_DIGIT}|{STRING}|[-._:/\,\[\]\{\}\ ])*({LETTER_OR_DIGIT}|\}|\])
%%
@@ -94,7 +97,7 @@ STRING \'([^'\\\n]|\\.|\'\')*\'
[ \t\r]+ /* eat whitespace */
#.* /* eat comment (.* matches anything until newline) */
-{ID} return GUC_ID;
+{AID} return GUC_ID;
{QUALIFIED_ID} return GUC_QUALIFIED_ID;
{STRING} return GUC_STRING;
{UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
@@ -421,8 +424,20 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
if (token == GUC_STRING) /* strip quotes and escapes */
opt_value = DeescapeQuotedString(yytext);
else
+ {
opt_value = pstrdup(yytext);
+ if (token == GUC_UNQUOTED_STRING && (opt_value[0] == '{' || opt_value[0] == '['))
+ {
+ /* mark name as composite */
+ int name_len = strlen(opt_name) + 3;
+ char *composite_name = (char *)palloc(name_len);
+ snprintf(composite_name, name_len, "%s->", opt_name);
+ pfree(opt_name);
+ opt_name = composite_name;
+ }
+ }
+
/* now we'd like an end of line, or possibly EOF */
token = yylex(scanner);
if (token != GUC_EOL)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 46fdefebe3..eac986a1c7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -35,6 +35,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_parameter_acl.h"
#include "guc_internal.h"
+#include "guc_composite.h"
#include "libpq/pqformat.h"
#include "libpq/protocol.h"
#include "miscadmin.h"
@@ -61,18 +62,16 @@
#define CONFIG_EXEC_PARAMS_NEW "global/config_exec_params.new"
#endif
-/*
- * Precision with which REAL type guc values are to be printed for GUC
- * serialization.
- */
-#define REALTYPE_PRECISION 17
-
/*
* Safe search path when executing code as the table owner, such as during
* maintenance operations.
*/
#define GUC_SAFE_SEARCH_PATH "pg_catalog, pg_temp"
+#define is_marked_scalar(record,item) (record->status & GUC_IS_IN_FILE)\
+ && !strchr(item->name, '-')\
+ && !strchr(item->name, '[')
+
static int GUC_check_errcode_value;
static List *reserved_class_prefix = NIL;
@@ -270,6 +269,8 @@ static bool call_string_check_hook(struct config_string *conf, char **newval,
void **extra, GucSource source, int elevel);
static bool call_enum_check_hook(struct config_enum *conf, int *newval,
void **extra, GucSource source, int elevel);
+static bool call_composite_check_hook(struct config_composite *conf, void *newval,
+ void **extra, GucSource source, int elevel);
/*
@@ -401,8 +402,13 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
if (record)
{
- /* If it's already marked, then this is a duplicate entry */
- if (record->status & GUC_IS_IN_FILE)
+ /*
+ * If it's already marked, then this is a duplicate entry However
+ * composite values are patches that could intersect or union like
+ * sets
+ */
+ if (is_marked_scalar(record, item)) /* do not optimize for
+ * composites */
{
/*
* Mark the earlier occurrence(s) as dead/ignorable. We could
@@ -741,6 +747,54 @@ set_string_field(struct config_string *conf, char **field, char *newval)
guc_free(oldval);
}
+
+/*
+ * Detect whether compositeval is referenced anywhere in a GUC struct item
+ */
+static bool
+composite_used(struct config_composite *conf, void *compositeval)
+{
+ GucStack *stack;
+
+ if (compositeval == conf->variable ||
+ compositeval == conf->reset_val ||
+ compositeval == conf->boot_val)
+ return true;
+
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (compositeval == stack->prior.val.compositeval ||
+ compositeval == stack->masked.val.compositeval)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Assign value to a composite GUC option. Free the prior value if it's not
+ * referenced anywhere else in the option values (including stacked states).
+ * Set flag "value_on_heap" to true to allocate new value.
+ */
+static void
+set_composite(struct config_composite *conf, void **field, void *newval, bool value_on_heap)
+{
+ void *oldval = *field;
+
+ /* Do the assignment */
+ if (value_on_heap)
+ *field = composite_dup(newval, conf->type_name);
+ else
+ {
+ free_composite_impl(*field, conf->type_name);
+ composite_dup_impl(*field, newval, conf->type_name);
+ }
+
+ /* Free old value if it's not NULL and isn't referenced anymore */
+ if (value_on_heap && oldval && !composite_used(conf, oldval))
+ free_composite(oldval, conf->type_name);
+}
+
/*
* Detect whether an "extra" struct is referenced anywhere in a GUC item
*/
@@ -773,6 +827,10 @@ extra_field_used(struct config_generic *gconf, void *extra)
if (extra == ((struct config_enum *) gconf)->reset_extra)
return true;
break;
+ case PGC_COMPOSITE:
+ if (extra == ((struct config_composite *) gconf)->reset_extra)
+ return true;
+ break;
}
for (stack = gconf->stack; stack; stack = stack->prev)
{
@@ -835,6 +893,14 @@ set_stack_value(struct config_generic *gconf, config_var_value *val)
val->val.enumval =
*((struct config_enum *) gconf)->variable;
break;
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+
+ set_composite((struct config_composite *) gconf,
+ &(val->val.compositeval),
+ ((struct config_composite *) gconf)->variable, value_on_heap);
+ }
}
set_extra_field(gconf, &(val->extra), gconf->extra);
}
@@ -859,6 +925,15 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
&(val->val.stringval),
NULL);
break;
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+
+ set_composite((struct config_composite *) gconf,
+ &(val->val.compositeval),
+ NULL, value_on_heap);
+ break;
+ }
}
set_extra_field(gconf, &(val->extra), NULL);
}
@@ -907,6 +982,12 @@ build_guc_variables(void)
int num_vars = 0;
HASHCTL hash_ctl;
GUCHashEntry *hentry;
+
+ int size_types;
+ int num_types = 0;
+ HASHCTL types_hash_ctl;
+ OptionTypeHashEntry *type_hentry;
+
bool found;
int i;
@@ -918,6 +999,12 @@ build_guc_variables(void)
"GUCMemoryContext",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Count all type defintions
+ */
+ for (i = 0; UserDefinedConfigureTypes[i].type_name; i++)
+ num_types++;
+
/*
* Count all the built-in variables, and set their vartypes correctly.
*/
@@ -962,9 +1049,46 @@ build_guc_variables(void)
num_vars++;
}
+ for (i = 0; ConfigureNamesComposite[i].gen.name; i++)
+ {
+ struct config_composite *conf = &ConfigureNamesComposite[i];
+
+ conf->gen.vartype = PGC_COMPOSITE;
+ num_vars++;
+ }
+
/*
- * Create hash table with 20% slack
+ * Create hash tables with 20% slack
*/
+
+ size_types = num_types + num_types / 4;
+ types_hash_ctl.keysize = sizeof(char *);
+ types_hash_ctl.entrysize = sizeof(OptionTypeHashEntry);
+ types_hash_ctl.hash = guc_name_hash;
+ types_hash_ctl.match = guc_name_match;
+ types_hash_ctl.hcxt = GUCMemoryContext;
+ guc_types_hashtab = hash_create("GUC user types hash table",
+ size_types,
+ &types_hash_ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+
+ for (i = 0; UserDefinedConfigureTypes[i].type_name; i++)
+ {
+ struct type_definition *type_definition = &UserDefinedConfigureTypes[i];
+
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab,
+ &type_definition->type_name,
+ HASH_ENTER,
+ &found);
+ Assert(!found);
+ type_hentry->definition = type_definition;
+
+ if (!is_scalar_type(type_definition->type_name))
+ init_type_definition(type_definition);
+ }
+
+ Assert(num_types == hash_get_num_entries(guc_types_hashtab));
+
size_vars = num_vars + num_vars / 4;
hash_ctl.keysize = sizeof(char *);
@@ -1037,6 +1161,17 @@ build_guc_variables(void)
hentry->gucvar = gucvar;
}
+ for (i = 0; ConfigureNamesComposite[i].gen.name; i++)
+ {
+ struct config_generic *gucvar = &ConfigureNamesComposite[i].gen;
+ hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+ &gucvar->name,
+ HASH_ENTER,
+ &found);
+ Assert(!found);
+ hentry->gucvar = gucvar;
+ }
+
Assert(num_vars == hash_get_num_entries(guc_hashtab));
}
@@ -1078,13 +1213,15 @@ valid_custom_variable_name(const char *name)
{
bool saw_sep = false;
bool name_start = true;
+ bool is_field = false;
+ const char *p;
- for (const char *p = name; *p; p++)
+ for (p = name; *p; p++)
{
if (*p == GUC_QUALIFIER_SEPARATOR)
{
- if (name_start)
- return false; /* empty name component */
+ if (name_start || is_field)
+ return false; /* empty name component or field of struct */
saw_sep = true;
name_start = true;
}
@@ -1097,10 +1234,40 @@ valid_custom_variable_name(const char *name)
}
else if (!name_start && strchr("0123456789$", *p) != NULL)
/* okay as non-first character */ ;
+ else if (*p == '-')
+ {
+ if (!name_start && *(p + 1) == '>')
+ {
+ name_start = true;
+ is_field = true;
+ p++;
+ }
+ else
+ return false;
+ }
+ else if (!name_start && *p == '[')
+ {
+ p++;
+ while (strchr("0123456789 ", *p) != NULL)
+ p++;
+
+ if ((*p == ']' && !*(p + 1)) || *(p + 1) == '-')
+ is_field = true;
+ else
+ return false;
+ }
else
return false;
}
- if (name_start)
+
+ /*
+ * Composite's name could be ended with '->', we should ignore last
+ * dereference This dirty hack is used to see difference between composite
+ * names and other GUCs It is important in case when we write placeholder
+ * for composite, cause rule of writing placeholders for composite and
+ * for other types are not the same
+ */
+ if (name_start && *(p - 1) != '>')
return false; /* empty name component */
/* OK if we found at least one separator */
return saw_sep;
@@ -1238,6 +1405,10 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
{
GUCHashEntry *hentry;
int i;
+ char *field_path_start = NULL;
+ char *first_bracket = NULL;
+ char *first_minus = NULL;
+ char *struct_name = NULL;
Assert(name);
@@ -1249,6 +1420,37 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
if (hentry)
return hentry->gucvar;
+ /*
+ * If value is a field of composite, then it's name is a path that
+ * consists of fields names, dereferences "->" and indexes [<number>]
+ */
+ first_bracket = strchr(name, '[');
+ first_minus = strchr(name, '-');
+
+ if (first_bracket != NULL || first_minus != NULL)
+ {
+ /* take first dereference */
+ if (((first_bracket < first_minus) && first_bracket != NULL) || first_minus == NULL)
+ field_path_start = first_bracket;
+ else
+ field_path_start = first_minus;
+
+ struct_name = guc_malloc(ERROR, (field_path_start - name + 1));
+ strncpy(struct_name, name, (field_path_start - name));
+ struct_name[field_path_start - name] = 0;
+
+ hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+ &struct_name,
+ HASH_FIND,
+ NULL);
+
+ if (hentry)
+ {
+ guc_free(struct_name);
+ return hentry->gucvar;
+ }
+ }
+
/*
* See if the name is an obsolete name for a variable. We assume that the
* set of supported old names is short enough that a brute-force search is
@@ -1267,11 +1469,24 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
* Check if the name is valid, and if so, add a placeholder.
*/
if (assignable_custom_variable_name(name, skip_errors, elevel))
+ {
+ if (field_path_start)
+ {
+ struct config_generic *result;
+
+ result = add_placeholder_variable(struct_name, elevel);
+ guc_free(struct_name);
+
+ return result;
+ }
+
return add_placeholder_variable(name, elevel);
+ }
else
return NULL; /* error message, if any, already emitted */
}
+ guc_free(struct_name);
/* Unknown name and we're not supposed to make a placeholder */
if (!skip_errors)
ereport(elevel,
@@ -1431,6 +1646,7 @@ check_GUC_name_for_parameter_acl(const char *name)
* real - can be 0.0, otherwise must be same as the boot_val
* string - can be NULL, otherwise must be strcmp equal to the boot_val
* enum - must be same as the boot_val
+ * struct - can be NULL, otherwise must be bitwise equal to the boot_val
*/
#ifdef USE_ASSERT_CHECKING
static bool
@@ -1499,6 +1715,38 @@ check_GUC_init(struct config_generic *gconf)
conf->gen.name, conf->boot_val, *conf->variable);
return false;
}
+ break;
+ }
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ bool is_null = true;
+ int type_size = get_composite_size(conf->type_name);
+
+ for (int i = 0; i < type_size; ++i)
+ {
+ if (*((char *) (conf->variable) + i) != 0)
+ {
+ is_null = false;
+ break;
+ }
+ }
+
+ if (!is_null && composite_cmp(conf->variable, conf->boot_val, conf->type_name))
+ {
+ bool not_write_to_file = false;
+ char *boot_str = composite_to_str(conf->boot_val, conf->type_name, not_write_to_file);
+ char *var_str = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ elog(LOG, "GUC (PGC_COMPOSITE %s) %s, boot_val=%s, C-var=%s",
+ conf->type_name, conf->gen.name, boot_str, var_str);
+
+ guc_free(boot_str);
+ guc_free(var_str);
+
+ return false;
+ }
+
break;
}
}
@@ -1747,6 +1995,65 @@ InitializeOneGUCOption(struct config_generic *gconf)
conf->assign_hook(newval, extra);
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
+ break;
+ }
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ void *newval;
+ void *extra = NULL;
+ void *var_buffer = NULL;
+ int valsize;
+
+ if (!conf->type_name && !conf->definition)
+ {
+ elog(FATAL, "failed to initialize %s. The type not specified",
+ conf->gen.name);
+
+ break;
+ }
+
+ if (!conf->type_name)
+ conf->type_name = conf->definition->type_name;
+
+ if (!conf->definition
+ && !is_static_array_type(conf->type_name)
+ && !is_dynamic_array_type(conf->type_name))
+ {
+ conf->definition = get_type_definition(conf->type_name);
+
+ if (!conf->definition)
+ {
+ elog(FATAL, "failed to initialize %s. Undefined type %s",
+ conf->gen.name, conf->type_name);
+
+ break;
+ }
+ }
+
+ /* non-NULL boot_val must always get compositedup'd */
+ if (conf->boot_val != NULL)
+ newval = composite_dup(conf->boot_val, conf->type_name);
+ else
+ newval = NULL;
+
+ if (!call_composite_check_hook(conf, newval, &extra,
+ PGC_S_DEFAULT, LOG))
+ elog(FATAL, "failed to initialize %s to %s",
+ conf->gen.name, newval ? composite_to_str(newval, conf->type_name, false) : "");
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, extra);
+
+ conf->reset_val = newval;
+
+ valsize = get_composite_size(conf->type_name);
+ var_buffer = composite_dup(newval, conf->type_name);
+ memcpy(conf->variable, var_buffer, valsize);
+ guc_free(var_buffer);
+
+ conf->gen.extra = conf->reset_extra = extra;
+
break;
}
}
@@ -2091,6 +2398,23 @@ ResetAllOptions(void)
conf->reset_extra);
break;
}
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ int valsize;
+
+ if (conf->assign_hook)
+ conf->assign_hook(conf->reset_val,
+ conf->reset_extra);
+
+ valsize = get_composite_size(conf->type_name);
+ memcpy(conf->variable, conf->reset_val, valsize);
+
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ conf->reset_extra);
+
+ break;
+ }
}
set_guc_source(gconf, gconf->reset_source);
@@ -2505,6 +2829,37 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
}
break;
}
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+ struct config_composite *conf = (struct config_composite *) gconf;
+ void *newval = newvalue.val.compositeval;
+ void *newextra = newvalue.extra;
+
+ if (composite_cmp(conf->variable, newval, conf->type_name) ||
+ conf->gen.extra != newextra)
+ {
+ bool value_not_on_heap = false;
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, newextra);
+
+ set_composite(conf, &conf->variable, newval, value_not_on_heap);
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ changed = true;
+ }
+
+ /*
+ * Release stacked values if not used anymore. We
+ * could use discard_stack_value() here, but since
+ * we have type-specific code anyway, might as
+ * well inline it.
+ */
+ set_composite(conf, &stack->prior.val.compositeval, NULL, value_on_heap);
+ set_composite(conf, &stack->masked.val.compositeval, NULL, value_on_heap);
+ break;
+ }
}
/*
@@ -3296,6 +3651,27 @@ parse_and_validate_value(struct config_generic *record,
return false;
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+ const char *hintmsg;
+
+ if (!parse_composite(value, conf->type_name, &newval->compositeval, conf->variable,
+ conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ conf->gen.name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return false;
+ }
+
+ if (!call_composite_check_hook(conf, newval->compositeval, newextra,
+ source, elevel))
+ return false;
+ }
+ break;
}
return true;
@@ -4012,9 +4388,76 @@ set_config_with_handle(const char *name, config_handle *handle,
if (value)
{
- if (!parse_and_validate_value(record, value,
- source, elevel,
- &newval_union, &newextra))
+ /* prepare placeholder for structure */
+ bool is_placeholder = false;
+ bool composite_value = false;
+ bool check_parsing;
+ char *prepared_strval = (char *) value; /* be careful with free */
+
+ /* check if string is a field of composite object or it */
+ if (strchr(name, '-') || strchr(name, '['))
+ is_placeholder = true;
+
+ if (suffix_is_arrow(name))
+ composite_value = true;
+
+ if (is_placeholder)
+ {
+ char *list;
+ char *old_list;
+ int list_length;
+
+ /*
+ * If value is a composite, name that ended with "->"
+ * All other values are scalar, they must be escaped
+ */
+ if (!composite_value)
+ {
+ char *escaped = escape_single_quotes_ascii(value);
+ int quoted_length = strlen(escaped) + 3;
+ char *quoted = guc_malloc(ERROR, quoted_length);
+
+ snprintf(quoted, quoted_length, "\'%s\'", escaped);
+
+ prepared_strval = convert_path_to_composite_value(name, quoted);
+
+ free(escaped);
+ guc_free(quoted);
+ }
+ else
+ prepared_strval = convert_path_to_composite_value(name, value);
+
+ /* get list from current value */
+ old_list = *(conf->variable);
+
+ if (old_list == NULL)
+ old_list = "";
+
+ /* Add value to placeholder patch list */
+ if (strlen(old_list) == 0)
+ {
+ list_length= strlen(prepared_strval) + 1;
+ list = guc_malloc(ERROR, list_length);
+ snprintf(list, list_length, "%s", prepared_strval);
+ }
+ else
+ {
+ list_length = strlen(old_list) + strlen(prepared_strval) + 2;
+ list = guc_malloc(ERROR, list_length);
+ snprintf(list, list_length, "%s;%s", old_list, prepared_strval);
+ }
+
+ guc_free(prepared_strval);
+ prepared_strval = list;
+ }
+
+ check_parsing = parse_and_validate_value(record, prepared_strval, source,
+ elevel, &newval_union, &newextra);
+
+ if (prepared_strval != value)
+ guc_free(prepared_strval);
+
+ if (!check_parsing)
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -4268,33 +4711,172 @@ set_config_with_handle(const char *name, config_handle *handle,
#undef newval
}
- }
-
- if (changeVal && (record->flags & GUC_REPORT) &&
- !(record->status & GUC_NEEDS_REPORT))
- {
- record->status |= GUC_NEEDS_REPORT;
- slist_push_head(&guc_report_list, &record->report_link);
- }
-
- return changeVal ? 1 : -1;
-}
-
-
-/*
- * Retrieve a config_handle for the given name, suitable for calling
- * set_config_with_handle(). Only return handle to permanent GUC.
- */
-config_handle *
-get_config_handle(const char *name)
-{
- struct config_generic *gen = find_option(name, false, false, 0);
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+ bool parse_result;
- if (gen && ((gen->flags & GUC_CUSTOM_PLACEHOLDER) == 0))
- return gen;
+#define newval (newval_union.compositeval)
+ if (value)
+ {
+ const char *str_val = (const char *) normalize_composite_value(name, value);
- return NULL;
-}
+ parse_result = parse_and_validate_value(record, str_val, source, elevel,
+ &newval_union, &newextra);
+
+ guc_free((void *) str_val);
+
+ if (!parse_result)
+ return 0;
+ }
+ else if (source == PGC_S_DEFAULT)
+ {
+ /* non-NULL boot_val must be compositedup'd */
+ if (conf->boot_val != NULL)
+ {
+ newval = composite_dup(conf->boot_val, conf->type_name);
+ if (newval == NULL)
+ return 0;
+ }
+ else
+ newval = NULL;
+
+ if (!call_composite_check_hook(conf, newval, &newextra,
+ source, elevel))
+ {
+ guc_free(newval);
+ return 0;
+ }
+ }
+ else
+ {
+ /*
+ * composite_dup not needed, since reset_val is already
+ * under guc.c's control
+ */
+ newval = conf->reset_val;
+ newextra = conf->reset_extra;
+ source = conf->gen.reset_source;
+ context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
+ }
+
+ if (prohibitValueChange)
+ {
+ bool newval_different;
+
+ /* newval shouldn't be NULL, so we're a bit sloppy here */
+ newval_different = (conf->variable == NULL ||
+ newval == NULL ||
+ composite_cmp(conf->variable, newval, conf->type_name) != 0);
+
+ /* Release newval, unless it's reset_val */
+ if (newval && !composite_used(conf, newval))
+ free_composite(newval, conf->type_name);
+ /* Release newextra, unless it's reset_extra */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ guc_free(newextra);
+
+ if (newval_different)
+ {
+ record->status |= GUC_PENDING_RESTART;
+ ereport(elevel,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed without restarting the server",
+ conf->gen.name)));
+ return 0;
+ }
+ record->status &= ~GUC_PENDING_RESTART;
+ return -1;
+ }
+
+ if (changeVal)
+ {
+ bool value_on_heap = false;
+
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen, action);
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, newextra);
+ set_composite(conf, &conf->variable, newval, value_on_heap);
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ set_guc_source(&conf->gen, source);
+ conf->gen.scontext = context;
+ conf->gen.srole = srole;
+ }
+
+ if (makeDefault)
+ {
+ bool value_on_heap = true;
+ GucStack *stack;
+
+ if (conf->gen.reset_source <= source)
+ {
+ set_composite(conf, &conf->reset_val, newval, value_on_heap);
+ set_extra_field(&conf->gen, &conf->reset_extra,
+ newextra);
+ conf->gen.reset_source = source;
+ conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
+ }
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (stack->source <= source)
+ {
+ set_composite(conf, &stack->prior.val.compositeval,
+ newval, value_on_heap);
+ set_extra_field(&conf->gen, &stack->prior.extra,
+ newextra);
+ stack->source = source;
+ stack->scontext = context;
+ stack->srole = srole;
+ }
+ }
+ }
+
+
+ /* Perhaps we didn't install newval anywhere */
+ if (newval && !composite_used(conf, newval))
+ free_composite(newval, conf->type_name);
+
+ /* Perhaps we didn't install newextra anywhere */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ {
+ guc_free(newextra);
+ break;
+ }
+#undef newval
+ }
+ }
+
+ if (changeVal && (record->flags & GUC_REPORT) &&
+ !(record->status & GUC_NEEDS_REPORT))
+ {
+ record->status |= GUC_NEEDS_REPORT;
+ slist_push_head(&guc_report_list, &record->report_link);
+ }
+
+ return changeVal ? 1 : -1;
+}
+
+
+/*
+ * Retrieve a config_handle for the given name, suitable for calling
+ * set_config_with_handle(). Only return handle to permanent GUC.
+ */
+config_handle *
+get_config_handle(const char *name)
+{
+ struct config_generic *gen = find_option(name, false, false, 0);
+
+ if (gen && ((gen->flags & GUC_CUSTOM_PLACEHOLDER) == 0))
+ return gen;
+
+ return NULL;
+}
/*
@@ -4395,6 +4977,14 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
case PGC_ENUM:
return config_enum_lookup_by_value((struct config_enum *) record,
*((struct config_enum *) record)->variable);
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ void *var = ((struct config_composite *) record)->variable;
+ const char *var_type = ((struct config_composite *) record)->type_name;
+
+ return var ? composite_to_str(var, var_type, not_write_to_file) : "nil";
+ }
}
return NULL;
}
@@ -4443,6 +5033,15 @@ GetConfigOptionResetString(const char *name)
case PGC_ENUM:
return config_enum_lookup_by_value((struct config_enum *) record,
((struct config_enum *) record)->reset_val);
+
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ void *val = ((struct config_composite *) record)->reset_val;
+ const char *val_type = ((struct config_composite *) record)->type_name;
+
+ return val ? composite_to_str(val, val_type, not_write_to_file) : "nil";
+ }
}
return NULL;
}
@@ -4493,25 +5092,52 @@ write_auto_conf_file(int fd, const char *filename, ConfigVariable *head)
errmsg("could not write to file \"%s\": %m", filename)));
}
- /* Emit each parameter, properly quoting the value */
+ /*
+ * Emit each parameter, quoting the value except when the option has a
+ * composite value
+ */
for (item = head; item != NULL; item = item->next)
{
+ bool is_struct = false;
char *escaped;
resetStringInfo(&buf);
- appendStringInfoString(&buf, item->name);
- appendStringInfoString(&buf, " = '");
+ /*
+ * Strutures names could be ended with "->" only in internal
+ * representation. So delete this suffix for user interface
+ */
+ if (item->name[strlen(item->name) - 2] == '-')
+ {
+ item->name[strlen(item->name) - 2] = 0;
+ appendStringInfoString(&buf, item->name);
+ is_struct = true;
+ item->name[strlen(item->name) - 2] = '-';
+ }
+ else
+ appendStringInfoString(&buf, item->name);
+ appendStringInfoString(&buf, " = ");
- escaped = escape_single_quotes_ascii(item->value);
- if (!escaped)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
- appendStringInfoString(&buf, escaped);
- free(escaped);
+ /*
+ * Append quotes for all scalar types But do not append qoutes for
+ * structure values
+ */
+ if (!is_struct)
+ {
+ appendStringInfoString(&buf, "\'");
+ escaped = escape_single_quotes_ascii(item->value);
+ if (!escaped)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ appendStringInfoString(&buf, escaped);
+ free(escaped);
+ appendStringInfoString(&buf, "\'");
+ }
+ else
+ appendStringInfoString(&buf, item->value);
- appendStringInfoString(&buf, "'\n");
+ appendStringInfoString(&buf, "\n");
errno = 0;
if (write(fd, buf.data, buf.len) != buf.len)
@@ -4546,6 +5172,9 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
*next,
*prev = NULL;
+ char *short_name = tokenize_field_path(pstrdup(name));
+ bool is_struct = strchr(name, '-');
+
/*
* Remove any existing match(es) for "name". Normally there'd be at most
* one, but if external tools have modified the config file, there could
@@ -4553,8 +5182,11 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
*/
for (item = *head_p; item != NULL; item = next)
{
+ char *tokenized_item_name = tokenize_field_path(pstrdup(item->name));
+
next = item->next;
- if (guc_name_compare(item->name, name) == 0)
+
+ if (guc_name_compare(tokenized_item_name, short_name) == 0)
{
/* found a match, delete it */
if (prev)
@@ -4571,15 +5203,23 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
}
else
prev = item;
+ pfree(tokenized_item_name);
}
/* Done if we're trying to delete it */
if (value == NULL)
+ {
+ pfree(short_name);
return;
+ }
/* OK, append a new entry */
item = palloc(sizeof *item);
- item->name = pstrdup(name);
+ if (is_struct)
+ item->name = pstrdup(name);
+ else
+ item->name = pstrdup(short_name);
+
item->value = pstrdup(value);
item->errmsg = NULL;
item->filename = pstrdup(""); /* new item has no location */
@@ -4593,6 +5233,7 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
else
(*tail_p)->next = item;
*tail_p = item;
+ pfree(short_name);
}
@@ -4633,6 +5274,17 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
switch (altersysstmt->setstmt->kind)
{
case VAR_SET_VALUE:
+ if (stmt_has_serialized_composite(altersysstmt->setstmt))
+ {
+ /* replace name for structs */
+ int composite_name_len = strlen(name) + 3;
+ char *composite_name = (char *) palloc(composite_name_len);
+
+ snprintf(composite_name, composite_name_len, "%s->", name);
+ pfree(name);
+ name = composite_name;
+ }
+
value = ExtractSetVariableArgs(altersysstmt->setstmt);
break;
@@ -4705,8 +5357,15 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
{
union config_var_val newval;
void *newextra = NULL;
+ char *prepared_value = value;
- if (!parse_and_validate_value(record, value,
+ /*
+ * For struct values we should convert path
+ */
+ if (record->vartype == PGC_COMPOSITE)
+ prepared_value = normalize_composite_value(name, value);
+
+ if (!parse_and_validate_value(record, prepared_value,
PGC_S_FILE, ERROR,
&newval, &newextra))
ereport(ERROR,
@@ -4714,8 +5373,74 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value)));
+ /*
+ * Each composite placeholder has -> in name For placeholder
+ * we using rules from set_config_with_handle Also cut off
+ * suffix of name
+ */
+ if (record->vartype == PGC_STRING && strchr(name, '-'))
+ {
+ struct config_string *conf = (struct config_string *) record;
+
+ if (value)
+ {
+ char *prepared = normalize_composite_value(name, value);
+ char *list;
+ char *old_list = *conf->variable ? *conf->variable : "";
+ int list_len = strlen(old_list) + strlen(prepared) + 2;
+
+ /* Add value to placeholder patch list */
+ list = guc_malloc(ERROR, list_len);
+ snprintf(list, list_len, "%s%s;", old_list, prepared);
+
+ pfree(value);
+ value = pstrdup(list);
+ guc_free(list);
+ guc_free(prepared);
+ }
+ tokenize_field_path(name);
+ }
+
if (record->vartype == PGC_STRING && newval.stringval != NULL)
+ {
guc_free(newval.stringval);
+ }
+
+ /*
+ * For struct we replace value with serialized parsed value
+ * Notice that patch is applied to current value
+ */
+ if (record->vartype == PGC_COMPOSITE)
+ {
+ char *serial_struct = NULL;
+ char *composite_name;
+ int composite_name_length;
+ bool write_to_file = true;
+
+ pfree(value);
+ serial_struct = composite_to_str(newval.compositeval, ((struct config_composite *) record)->type_name, write_to_file);
+ if (serial_struct == NULL)
+ value = NULL;
+ else
+ {
+ value = pstrdup(serial_struct);
+ guc_free(serial_struct);
+ }
+ free_composite(newval.compositeval, ((struct config_composite *) record)->type_name);
+
+ /*
+ * replace name with struct name + "->"" We need suffix to
+ * detect structure in replace_auto_config_value and
+ * write_auto_conf_file
+ */
+ name = tokenize_field_path(name);
+ composite_name_length = strlen(name) + 3;
+ composite_name = (char *) palloc(composite_name_length);
+
+ snprintf(composite_name, composite_name_length, "%s->", name);
+ pfree(name);
+ name = composite_name;
+ }
guc_free(newextra);
}
}
@@ -4734,6 +5459,25 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
*/
if (value || !valid_custom_variable_name(name))
(void) assignable_custom_variable_name(name, false, ERROR);
+
+ /*
+ * If option is composite, we can understand it with "->" or "[]"
+ * in name
+ */
+ if (strchr(name, '-') || strchr(name, '['))
+ {
+ char *prepared = normalize_composite_value(name, value);
+
+ pfree(value);
+ value = pstrdup(prepared);
+ guc_free(prepared);
+
+ /*
+ * prepare name to replace_auto_config_value We set -> at the
+ * end of name to distinguish placeholders for composites
+ */
+ tokenize_field_path(name);
+ }
}
/*
@@ -4944,6 +5688,15 @@ define_custom_variable(struct config_generic *variable)
const char *name = variable->name;
GUCHashEntry *hentry;
struct config_string *pHolder;
+ char *name_with_suffix = (char *) name;
+
+ if (variable->vartype == PGC_COMPOSITE)
+ {
+ int name_with_suffix_len = strlen(name) + 3;
+
+ name_with_suffix = guc_malloc(ERROR, name_with_suffix_len);
+ snprintf(name_with_suffix, name_with_suffix_len, "%s->", name);
+ }
/* Check mapping between initial and default value */
Assert(check_GUC_init(variable));
@@ -5009,7 +5762,7 @@ define_custom_variable(struct config_generic *variable)
/* First, apply the reset value if any */
if (pHolder->reset_val)
- (void) set_config_option_ext(name, pHolder->reset_val,
+ (void) set_config_option_ext(name_with_suffix, pHolder->reset_val,
pHolder->gen.reset_scontext,
pHolder->gen.reset_source,
pHolder->gen.reset_srole,
@@ -5030,6 +5783,9 @@ define_custom_variable(struct config_generic *variable)
/* Now we can free the no-longer-referenced placeholder variable */
free_placeholder(pHolder);
+
+ if (name_with_suffix != name)
+ guc_free(name_with_suffix);
}
/*
@@ -5049,6 +5805,15 @@ reapply_stacked_values(struct config_generic *variable,
{
const char *name = variable->name;
GucStack *oldvarstack = variable->stack;
+ char *name_with_suffix = (char *) name;
+
+ if (variable->vartype == PGC_COMPOSITE)
+ {
+ int name_with_suffix_len = strlen(name) + 3;
+
+ name_with_suffix = guc_malloc(ERROR, name_with_suffix_len);
+ snprintf(name_with_suffix, name_with_suffix_len, "%s->", name);
+ }
if (stack != NULL)
{
@@ -5061,21 +5826,21 @@ reapply_stacked_values(struct config_generic *variable,
switch (stack->state)
{
case GUC_SAVE:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SAVE, true,
WARNING, false);
break;
case GUC_SET:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SET, true,
WARNING, false);
break;
case GUC_LOCAL:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_LOCAL, true,
WARNING, false);
@@ -5083,14 +5848,14 @@ reapply_stacked_values(struct config_generic *variable,
case GUC_SET_LOCAL:
/* first, apply the masked value as SET */
- (void) set_config_option_ext(name, stack->masked.val.stringval,
+ (void) set_config_option_ext(name_with_suffix, stack->masked.val.stringval,
stack->masked_scontext,
PGC_S_SESSION,
stack->masked_srole,
GUC_ACTION_SET, true,
WARNING, false);
/* then apply the current value as LOCAL */
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_LOCAL, true,
WARNING, false);
@@ -5116,7 +5881,7 @@ reapply_stacked_values(struct config_generic *variable,
cursource != pHolder->gen.reset_source ||
cursrole != pHolder->gen.reset_srole)
{
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SET, true, WARNING, false);
if (variable->stack != NULL)
@@ -5126,6 +5891,9 @@ reapply_stacked_values(struct config_generic *variable,
}
}
}
+
+ if (name != name_with_suffix)
+ guc_free(name_with_suffix);
}
/*
@@ -5289,6 +6057,66 @@ DefineCustomEnumVariable(const char *name,
define_custom_variable(&var->gen);
}
+void
+DefineCustomCompositeVariable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ const char *type_name,
+ void *valueAddr,
+ const void *bootValueAddr,
+ GucContext context,
+ int flags,
+ GucCompositeCheckHook check_hook,
+ GucCompositeAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ struct config_composite *var;
+ struct type_definition *type_definition = NULL;
+
+ if (!is_static_array_type(type_name) && !is_dynamic_array_type(type_name))
+ {
+ type_definition = get_type_definition(type_name);
+ if (type_definition == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("option %s has undefined type %s", name, type_name)));
+ }
+
+ var = (struct config_composite *)
+ init_custom_variable(name, short_desc, long_desc, context, flags,
+ PGC_COMPOSITE, sizeof(struct config_composite));
+ var->variable = valueAddr;
+ var->type_name = type_name;
+ var->boot_val = bootValueAddr;
+ var->check_hook = check_hook;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ var->definition = type_definition;
+ define_custom_variable(&var->gen);
+}
+
+void
+DefineCustomCompositeType(const char *type_name, const char *signature)
+{
+ bool found;
+ OptionTypeHashEntry *type_hentry;
+ struct type_definition *type_definition = (struct type_definition *) guc_malloc(ERROR, sizeof(struct type_definition));
+
+ memset(type_definition, 0, sizeof(struct type_definition));
+ type_definition->type_name = type_name;
+ type_definition->signature = signature;
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab,
+ &type_definition->type_name,
+ HASH_ENTER,
+ &found);
+
+ Assert(!found);
+ type_hentry->definition = type_definition;
+
+ if (!is_scalar_type(type_definition->type_name))
+ init_type_definition(type_definition);
+}
+
/*
* Mark the given GUC prefix as "reserved".
*
@@ -5429,6 +6257,22 @@ get_explain_guc_options(int *num)
modified = (lconf->boot_val != *(lconf->variable));
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *lconf = (struct config_composite *) conf;
+
+ if (lconf->boot_val == NULL &&
+ lconf->variable == NULL)
+ modified = false;
+ else if (lconf->boot_val == NULL ||
+ lconf->variable == NULL)
+ modified = true;
+ else
+ {
+ modified = (composite_cmp(lconf->boot_val, lconf->variable, lconf->type_name) != 0);
+ }
+ }
+ break;
default:
elog(ERROR, "unexpected GUC type: %d", conf->vartype);
@@ -5454,6 +6298,8 @@ char *
GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
{
struct config_generic *record;
+ char *first_minus = strchr(name, '-');
+ char *first_bracket = strchr(name, '[');
record = find_option(name, false, missing_ok, ERROR);
if (record == NULL)
@@ -5473,6 +6319,39 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (varname)
*varname = record->name;
+ /*
+ * For structure fields we use custom show function. yes it's so sloppy
+ * but SHowGucOption can't use string as parameter
+ */
+ if (first_minus || first_bracket)
+ {
+ void *structp = NULL;
+ char *field_type;
+ char *str_opt;
+ char *result;
+ struct config_composite *conf = (struct config_composite *) record;
+
+ if (conf->show_hook)
+ result = pstrdup(conf->show_hook());
+ else
+ {
+ bool not_write_to_file = false;
+
+ /* find start of structure and convert it to struct */
+ structp = get_nested_field_ptr(conf->variable, conf->type_name, name);
+ field_type = get_nested_field_type_name(conf->type_name, name);
+ str_opt = composite_to_str(structp, field_type, not_write_to_file);
+
+ /* copy result */
+ result = pstrdup(str_opt);
+
+ /* free work structures */
+ guc_free(field_type);
+ guc_free(str_opt);
+ }
+ return result;
+ }
+
return ShowGUCOption(record, true);
}
@@ -5580,6 +6459,27 @@ ShowGUCOption(struct config_generic *record, bool use_units)
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+
+ if (conf->show_hook)
+ val = conf->show_hook();
+ else if (conf->variable)
+ {
+ bool not_write_to_file = false;
+ char *result;
+ char *value = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ result = pstrdup(value);
+ guc_free(value);
+ return result; /* yes it's awful, so the shortest way
+ * that I found */
+ }
+ else
+ val = "nil";
+ }
+ break;
default:
/* just to keep compiler quiet */
val = "???";
@@ -5658,6 +6558,21 @@ write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+
+ if (conf->variable)
+ {
+ bool not_write_to_file = false;
+ char *serialized = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ fprintf(fp, "%s", serialized);
+ guc_free(serialized);
+ }
+ }
+ break;
}
fputc(0, fp);
@@ -5940,6 +6855,16 @@ estimate_variable_size(struct config_generic *gconf)
valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+
+ if (conf->variable)
+ valsize = get_length_composite_str(conf->variable, conf->type_name);
+ else
+ valsize = 0;
+ }
}
/* Allow space for terminating zero-byte for value */
@@ -6098,6 +7023,16 @@ serialize_variable(char **destptr, Size *maxbytes,
config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ struct config_composite *conf = (struct config_composite *) gconf;
+ char *struct_str = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ do_serialize(destptr, maxbytes, "%s", struct_str);
+ guc_free(struct_str);
+ }
}
do_serialize(destptr, maxbytes, "%s",
@@ -6315,6 +7250,17 @@ RestoreGUCState(void *gucstate)
guc_free(conf->reset_extra);
break;
}
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *)gconf;
+
+ free_composite(conf->variable, conf->type_name);
+ if (conf->reset_val && conf->reset_val != conf->variable)
+ free_composite(conf->reset_val, conf->type_name);
+ if (conf->reset_extra && conf->reset_extra != gconf->extra)
+ guc_free(conf->reset_extra);
+ break;
+ }
}
/* Remove it from any lists it's in. */
RemoveGUCFromLists(gconf);
@@ -7008,3 +7954,53 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+static bool
+call_composite_check_hook(struct config_composite *conf, void *newval, void **extra,
+ GucSource source, int elevel)
+{
+ volatile bool result = true;
+
+ /* Quick success if no hook */
+ if (!conf->check_hook)
+ return true;
+
+ /*
+ * If elevel is ERROR, or if the check_hook itself throws an elog
+ * (undesirable, but not always avoidable), make sure we don't leak the
+ * already-malloc'd newval struct.
+ */
+ PG_TRY();
+ {
+ /* Reset variables that might be set by hook */
+ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
+ GUC_check_errmsg_string = NULL;
+ GUC_check_errdetail_string = NULL;
+ GUC_check_errhint_string = NULL;
+
+ if (!conf->check_hook(newval, extra, source))
+ {
+ ereport(elevel,
+ (errcode(GUC_check_errcode_value),
+ GUC_check_errmsg_string ?
+ errmsg_internal("%s", GUC_check_errmsg_string) :
+ errmsg("invalid value for parameter \"%s\": %s",
+ conf->gen.name, newval ? composite_to_str(newval, conf->type_name, false) : ""),
+ GUC_check_errdetail_string ?
+ errdetail_internal("%s", GUC_check_errdetail_string) : 0,
+ GUC_check_errhint_string ?
+ errhint("%s", GUC_check_errhint_string) : 0));
+ /* Flush any strings created in ErrorContext */
+ FlushErrorState();
+ result = false;
+ }
+ }
+ PG_CATCH();
+ {
+ guc_free(newval);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return result;
+}
diff --git a/src/backend/utils/misc/guc_composite.c b/src/backend/utils/misc/guc_composite.c
new file mode 100644
index 0000000000..81319640f7
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite.c
@@ -0,0 +1,1486 @@
+/*--------------------------------------------------------------------
+ * guc_composite.c
+ *
+ * This file contains the implementation of functions
+ * related to the custom composite type system.
+ *
+ * The functions are divided into 3 groups:
+ * 1. registration and support for composite types
+ * 2. support for composite options
+ * 3. printing composite options
+ *
+ * See src/backend/utils/misc/README for more information.
+ *
+ * Copyright (c) 2000-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite.c
+ *
+ *--------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <float.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ucontext.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "guc_composite.h"
+#include "utils/builtins.h"
+#include "lib/stringinfo.h"
+
+
+bool extended_array_view;
+
+#define STRUCT_FIELDS_DELIMETER ';'
+#define STRUCT_FIELDS_DELIMETER_STR ";"
+
+#define element_of_data_of_dynamic_array(type_name, field) (is_dynamic_array_type(type_name) &&\
+ strcmp(field,"data") != 0 &&\
+ strcmp(field, "size") != 0)
+
+/*
+ * That structure is template of dynamic array representation in C code
+ * It is used only to get sizes and offsets of it's fields
+ */
+struct DynArrTmp
+{
+ void *data;
+ int size;
+};
+
+/* The global hash table of definitions of composite types for guc options */
+HTAB *guc_types_hashtab;
+
+static int get_type_offset(const char *type_name);
+static char *get_struct_field_type(const char *type_name, const char *field);
+static char *print_empty_array(bool writing_to_file, bool extend);
+static char *array_to_str(const void *data, int size, const char *type, bool writing_to_file, bool extend);
+static char *static_array_to_str(const void *structp, const char *type, bool writing_to_file);
+static char *dynamic_array_to_str(const void *structp, const char *type, bool writing_to_file);
+static char *scalar_to_str(const void *structp, const char *type_name, bool writing_to_file);
+static char *struct_to_str(const void *structp, const char *type, bool writing_to_file);
+static void static_array_dup(void *dest_struct, const void *src_struct, const char *type_name);
+static void struct_dup(void *dest_struct, const void *src_struct, const char *type_name);
+static int array_data_cmp(const void *first, const void *second, const char *type, int size);
+static int dynamic_array_cmp(const void *first, const void *second, const char *type);
+static int struct_cmp(const void *first, const void *second, const char *type);
+static void free_static_array(void *delptr, const char *type);
+static void free_dynamic_array(void *delptr, const char *type);
+static void free_struct(void *delptr, const char *type);
+static void dynamic_array_dup(void *dest_struct, const void *src_struct, const char *type);
+static int get_dynamic_array_size(const char *type_name, const void *structp);
+
+
+int
+get_static_array_length(const char *type_name)
+{
+ char *length_str_begin = strchr(type_name, '[');
+ char *length_str_end = NULL;
+ char *parsed_end = NULL;
+ long length = 0;
+
+ if (length_str_begin == NULL)
+ return 0;
+
+ length_str_begin++;
+
+ length_str_end = strchr(length_str_begin, ']');
+
+ if (!length_str_end)
+ return 0;
+
+ length = strtol(length_str_begin, &parsed_end, 10);
+
+ if (errno == ERANGE)
+ return 0;
+
+ while (parsed_end != length_str_end)
+ {
+ if (!isblank(*parsed_end++))
+ return 0;
+ }
+
+ return length;
+}
+
+bool
+is_static_array_type(const char *type_name)
+{
+ return (bool) get_static_array_length(type_name);
+}
+
+bool
+is_dynamic_array_type(const char *type_name)
+{
+ char *size_str_begin = strchr(type_name, '[');
+
+ if (size_str_begin && *(size_str_begin + 1) == ']')
+ return true;
+
+ return false;
+}
+
+char *
+get_array_basic_type(const char *array_type)
+{
+ ptrdiff_t first_part_len;
+ ptrdiff_t second_part_len;
+ size_t type_len;
+ char *type_name;
+ const char *bracket_close;
+ const char *bracket_open = strchr(array_type, '[');
+
+ if (!bracket_open)
+ return NULL;
+
+ bracket_close = strchr(bracket_open, ']');
+
+ if (!bracket_close)
+ return NULL;
+
+ first_part_len = bracket_open - array_type;
+ second_part_len = strchr(bracket_close, '\0') - bracket_close;
+ type_len = first_part_len + second_part_len;
+
+ type_name = guc_malloc(ERROR, type_len);
+ strncpy(type_name, array_type, first_part_len);
+ strncpy(type_name + first_part_len, bracket_close + 1, second_part_len);
+
+ return type_name;
+}
+
+struct type_definition *
+get_type_definition(const char *type_name)
+{
+ struct type_definition *definition;
+ bool found = false;
+ OptionTypeHashEntry *type_hentry = NULL;
+
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab, &type_name, HASH_FIND, &found);
+
+ if (found)
+ {
+ definition = type_hentry->definition;
+
+ return definition;
+ }
+
+ return NULL;
+}
+
+int
+get_array_size(const char *type_name, const int length)
+{
+ int array_size;
+ int element_size;
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type || length < 0)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+ element_size = get_composite_size(basic_type);
+ guc_free(basic_type);
+
+ if (element_offset < 0 || element_size < 0)
+ return -1;
+
+ array_size = length * (element_size + (element_size % element_offset));
+
+ return array_size;
+}
+
+static int
+get_static_array_size(const char *type_name)
+{
+ int length = get_static_array_length(type_name);
+
+ return get_array_size(type_name, length);
+}
+
+static int
+get_dynamic_array_size(const char *type_name, const void *structp)
+{
+ int length = dynamic_array_size(structp);
+
+ return get_array_size(type_name, length);
+}
+
+static int
+get_struct_size(const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type_name)))
+ return struct_type->type_size;
+
+ return -1;
+}
+
+int
+get_composite_size(const char *type_name)
+{
+ if (!type_name)
+ return -1;
+
+ /*
+ * Dynamic array is a struct that has 2 fields: pointer, int (see
+ * DynArrTmp) Do not use this function for getting size of allocated data
+ * of dynamic array
+ */
+ if (is_dynamic_array_type(type_name))
+ return sizeof(struct DynArrTmp);
+
+ if (is_static_array_type(type_name))
+ return get_static_array_size(type_name);
+
+ return get_struct_size(type_name);
+}
+
+static int
+get_array_offset(const char *type_name)
+{
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+
+ if (element_offset < 0)
+ return -1;
+
+ guc_free(basic_type);
+
+ return element_offset;
+}
+
+static int
+get_struct_offset(const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return -1;
+
+ return struct_type->offset;
+}
+
+static int
+get_type_offset(const char *type_name)
+{
+ if (!type_name)
+ return -1;
+
+ /*
+ * Dynamic array in struct that is 2 fields: pointer, int Therefore offset
+ * of pointer, int and offset of the pointer are same
+ */
+ if (is_dynamic_array_type(type_name))
+ return sizeof(void *);
+
+ if (is_static_array_type(type_name))
+ return get_array_offset(type_name);
+
+ return get_struct_offset(type_name);
+}
+
+static char *
+get_struct_field_type(const char *type_name, const char *field)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return NULL;
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ if (strcmp(field, struct_type->fields[i].name) == 0)
+ return guc_strdup(ERROR, struct_type->fields[i].type);
+ }
+
+ return NULL;
+}
+
+char *
+get_field_type_name(const char *type_name, const char *field)
+{
+ if (!type_name || !field)
+ return NULL;
+
+ /*
+ * if field is "data" or "size", dynamic array is DynArrTmp else dynamic
+ * array is allocated data
+ */
+ if (is_dynamic_array_type(type_name))
+ {
+ if (strcmp(field, "size") == 0)
+ return guc_strdup(ERROR, "int");
+
+ if (strcmp(field, "data") == 0)
+ return guc_strdup(ERROR, type_name);
+
+ return get_array_basic_type(type_name);
+ }
+
+ if (is_static_array_type(type_name))
+ return get_array_basic_type(type_name);
+
+ return get_struct_field_type(type_name, field);
+}
+
+int
+get_element_offset(const char *type_name, int index)
+{
+ int element_size;
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type || index < 0)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+ element_size = get_composite_size(basic_type);
+ guc_free(basic_type);
+
+ if (element_offset < 0 || element_size < 0)
+ return -1;
+
+ return element_size * index;
+}
+
+static int
+get_element_offset_with_parse_index(const char *type_name, const char *field)
+{
+ char *parsed_end = NULL;
+ long field_idx = -1;
+
+ field_idx = strtol(field, &parsed_end, 10);
+
+ if (errno == ERANGE)
+ return -1;
+
+ while (*parsed_end)
+ {
+ if (!isblank(*parsed_end++))
+ return -1;
+ }
+
+ return get_element_offset(type_name, (int) field_idx);
+}
+
+static int
+get_struct_field_offset(const char *type_name, const char *field_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type_name)) == NULL)
+ return -1;
+
+ for (int i = 0, total_offset = 0; i < struct_type->cnt_fields; ++i)
+ {
+ int increment;
+ int local_off = get_type_offset(struct_type->fields[i].type);
+
+ if (local_off < 0)
+ return -1;
+
+ if (total_offset % local_off != 0)
+ total_offset += local_off - total_offset % local_off;
+
+ if (strcmp(struct_type->fields[i].name, field_name) == 0)
+ return total_offset;
+
+ increment = get_composite_size(struct_type->fields[i].type);
+ total_offset += increment;
+ }
+
+ return -1;
+}
+
+int
+get_field_offset(const char *type_name, const char *field_name)
+{
+ if (!type_name || !field_name)
+ return -1;
+
+ /*
+ * if field_name is "data" or "size", composite value is a DynArrTmp else
+ * composite value is allocated data for dynamic array (Attention! This
+ * function cannot check length of dynamic array)
+ */
+ if (is_dynamic_array_type(type_name))
+ {
+ if (strcmp(field_name, "data") == 0)
+ return offsetof(struct DynArrTmp, data);
+ else if (strcmp(field_name, "size") == 0)
+ return offsetof(struct DynArrTmp, size);
+ else
+ return get_element_offset_with_parse_index(type_name, field_name);
+ }
+
+ if (is_static_array_type(type_name))
+ return get_element_offset_with_parse_index(type_name, field_name);
+
+ return get_struct_field_offset(type_name, field_name);
+}
+
+void
+init_type_definition(struct type_definition *definition)
+{
+ const char *word_del = " \t\n\v";
+ int max_offset = 0;
+ int count_fields = 1;
+ struct_field *fields = NULL; /* meta about fields */
+ char *signature_saveptr;
+ char *field_def_saveptr;
+ char *signature,
+ *field_def;
+ char *field_def_token;
+ char *word_token;
+ char *signature_buffer;
+ int curr_offset = 0;
+ int i;
+
+ /* count fields in signature */
+ const char *sym = definition->signature;
+
+ if (!sym || !*sym)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("signature of \"%s\" type is empty", definition->type_name));
+
+ return;
+ }
+
+ while (*sym)
+ {
+ if (*sym == STRUCT_FIELDS_DELIMETER)
+ count_fields++;
+
+ sym++;
+ }
+
+ /* allocate structures for field definitions */
+ fields = (struct_field *) guc_malloc(ERROR, count_fields * sizeof(struct_field));
+
+ /* parse signature */
+
+ signature = guc_strdup(ERROR, definition->signature);
+ signature_buffer = signature;
+
+ /* parse sequence of structure field definitions */
+ for (i = 0;; i++, signature = NULL)
+ {
+ int parsed_word_no = 0;
+
+ field_def_token = strtok_r(signature, STRUCT_FIELDS_DELIMETER_STR, &signature_saveptr);
+
+ if (!field_def_token)
+ break;
+
+ /*
+ * Parse field definition First word is a type, second is a name of
+ * field. Definitions are separated with STRUCT_FIELDS_DELIMETER
+ */
+ for (field_def = field_def_token;; field_def = NULL)
+ {
+ word_token = strtok_r(field_def, word_del, &field_def_saveptr);
+
+ if (!word_token)
+ {
+ if (parsed_word_no != 2)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong field definition: \"%s\" in definition of type \"%s\"",
+ field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ break;
+ }
+
+ word_token = guc_strdup(ERROR, word_token);
+
+ /* parse field type */
+ if (parsed_word_no == 0)
+ {
+ int type_offset = get_type_offset(word_token);
+ int type_size = get_composite_size(word_token);
+
+ if (type_offset < 0 || type_size < 0)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong type \"%s\"is used in field definition: \"%s\" in definition of type \"%s\"",
+ word_token, field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ fields[i].type = word_token;
+
+ /* structure offset = max offset of field offsets */
+ if (type_offset > max_offset)
+ max_offset = type_offset;
+
+ /* field offset in structure % field type offset = 0 */
+ if (curr_offset % type_offset != 0)
+ curr_offset += type_offset - curr_offset % type_offset;
+
+ curr_offset += type_size;
+ }
+ else if (parsed_word_no == 1) /* parse field name */
+ fields[i].name = word_token;
+ else
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong field definition: \"%s\" in definition of type \"%s\"",
+ field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ parsed_word_no++;
+ }
+ }
+
+ /* structure size % structure offset = 0 */
+ if (curr_offset % max_offset != 0)
+ curr_offset += max_offset - curr_offset % max_offset;
+
+ definition->offset = max_offset;
+ definition->type_size = curr_offset;
+ definition->cnt_fields = count_fields;
+ definition->fields = fields;
+ fields = NULL;
+ word_token = NULL;
+
+out:
+ guc_free(fields);
+ guc_free(word_token);
+ guc_free(signature_buffer);
+
+ return;
+}
+
+char *
+get_nested_field_type_name(const char *type_name, const char *field_path)
+{
+ char *path;
+ char *cur_type_name;
+ char *cur_field;
+
+ if (!type_name || !field_path)
+ return NULL;
+
+ path = guc_strdup(ERROR, field_path);
+ cur_type_name = guc_strdup(ERROR, type_name);
+ cur_field = tokenize_field_path(path);
+ cur_field = tokenize_field_path(NULL); /* skip name of structure name */
+
+ /* Follow the path of the field */
+ while (cur_field && cur_type_name)
+ {
+ char *next_type = get_field_type_name(cur_type_name, cur_field);
+
+ guc_free(cur_type_name);
+ cur_type_name = next_type;
+
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ guc_free(path);
+
+ return cur_type_name;
+}
+
+void *
+get_nested_field_ptr(const void *composite_start, const char *type_name, const char *field_path)
+{
+ char *path;
+ char *cur_type_name;
+ char *cur_field;
+ char *cur_ptr;
+
+ if (!composite_start || !field_path || !type_name)
+ return NULL;
+
+ path = guc_strdup(ERROR, field_path);
+ cur_type_name = guc_strdup(ERROR, type_name);
+
+ cur_field = tokenize_field_path(path);
+ cur_field = tokenize_field_path(NULL); /* skip name of structure */
+ cur_ptr = (char *) composite_start;
+
+ while (cur_field && cur_type_name)
+ {
+ char *next_type;
+ int local_offset;
+
+ /* go to memory of dynamic array */
+ if (element_of_data_of_dynamic_array(cur_type_name, cur_field))
+ cur_ptr = *((char **) cur_ptr);
+
+ local_offset = get_field_offset(cur_type_name, cur_field);
+
+ if (local_offset < 0)
+ {
+ cur_ptr = NULL;
+ break;
+ }
+
+ cur_ptr += local_offset;
+ next_type = get_field_type_name(cur_type_name, cur_field);
+
+ guc_free(cur_type_name);
+ cur_type_name = next_type;
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ guc_free(path);
+ guc_free(cur_type_name);
+
+ return cur_ptr;
+}
+
+static char *
+print_empty_array(bool writing_to_file, bool extend)
+{
+ if (extend)
+ {
+ if (writing_to_file)
+ return guc_strdup(ERROR, "{size: 0, data: []}");
+ else
+ return guc_strdup(ERROR, "{\n\tsize: 0,\n\tdata: []\n}");
+ }
+ else
+ return guc_strdup(ERROR, "[]");
+}
+
+static char *
+array_to_str(const void *data, int size, const char *type, bool writing_to_file, bool extend)
+{
+ const char *tab_prefix = extend ? "\t\t" : "\t";
+ StringInfoData buf;
+ char *result = NULL;
+ char *element_type;
+
+ /* process empty array */
+ if (!size)
+ print_empty_array(writing_to_file, extend);
+
+ initStringInfo(&buf);
+
+ element_type = get_array_basic_type(type);
+
+ /* write prefix */
+ if (extend)
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "{size: %d, data: [", size);
+ else
+ appendStringInfo(&buf, "{\n\tsize: %d,\n\tdata: [\n", size);
+ }
+ else
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "[");
+ else
+ appendStringInfo(&buf, "[\n");
+ }
+
+ /* recursive call for each element of array */
+ for (int i = 0; i < size; i++)
+ {
+ char *element;
+ int offset = get_element_offset(type, i);
+
+ if (offset < 0)
+ goto out;
+
+ element = composite_to_str((char *) data + offset, element_type, writing_to_file);
+
+ if (!element)
+ goto out;
+
+ if (writing_to_file)
+ {
+ appendStringInfo(&buf, "%s", element);
+
+ if (i < size - 1)
+ appendStringInfo(&buf, ", ");
+ }
+ else
+ {
+ /*
+ * if not writing to file, add tab_prefix at the beginning of each
+ * line
+ */
+ char *str_saveptr;
+ char *str_begin = strtok_r(element, "\n", &str_saveptr);
+
+ appendStringInfo(&buf, "%s%s", tab_prefix, str_begin);
+
+ while ((str_begin = strtok_r(NULL, "\n", &str_saveptr)) != NULL)
+ appendStringInfo(&buf, "\n%s%s", tab_prefix, str_begin);
+
+ if (i < size - 1)
+ appendStringInfo(&buf, ",\n");
+ else
+ appendStringInfo(&buf, "\n");
+ }
+
+ guc_free(element);
+ }
+
+ /* write suffix */
+ if (extend)
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "]}");
+ else
+ appendStringInfo(&buf, "\t]\n}");
+ }
+ else
+ appendStringInfo(&buf, "]");
+
+ result = guc_strdup(ERROR, buf.data);
+
+out:
+ pfree(buf.data);
+ guc_free(element_type);
+
+ return result;
+}
+
+static char *
+static_array_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ int array_size = get_static_array_length(type);
+
+ return array_to_str(structp, array_size, type, writing_to_file, false);
+}
+
+static char *
+dynamic_array_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ int array_size = dynamic_array_size(structp);
+
+ /* extended_array_view - global variable (GUC) */
+ return array_to_str(*(void **) structp,
+ array_size,
+ type,
+ writing_to_file,
+ extended_array_view);
+}
+
+static char *
+scalar_to_str(const void *structp, const char *type_name, bool writing_to_file)
+{
+ char *buf;
+ char *quoted;
+
+ if (strcmp(type_name, "bool") == 0)
+ {
+ int maxlen = 6;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+
+ if (*(bool *) structp)
+ snprintf(buf, maxlen, "%s", "true");
+ else
+ snprintf(buf, maxlen, "%s", "false");
+ }
+ else if (strcmp(type_name, "int") == 0)
+ {
+ int maxlen = 12;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+ snprintf(buf, maxlen, "%d", *(int *) structp);
+ }
+ else if (strcmp(type_name, "real") == 0)
+ {
+ int maxlen = DBL_MAX_10_EXP + 3;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+ snprintf(buf, maxlen, "%lf", *(double *) structp);
+ }
+ else if (strcmp(type_name, "string") == 0)
+ {
+ if (*(char **) structp == NULL)
+ buf = guc_strdup(ERROR, "nil");
+ else
+ {
+ /* escape quotes only if writing to file */
+ if (writing_to_file)
+ {
+ char *escaped = escape_single_quotes_ascii(*(char **) structp);
+
+ buf = guc_strdup(ERROR, escaped);
+ free(escaped);
+ }
+ else
+ buf = guc_strdup(ERROR, *(char **) structp);
+ }
+ }
+ else
+ return NULL;
+
+ /*
+ * add apostrophes: If write to file, add apostrophes for each type Else
+ * add apostrophes only for strings
+ */
+ if (writing_to_file || (strcmp(type_name, "string") == 0 && strcmp(buf, "nil") != 0))
+ {
+ int maxlen = strlen(buf) + 3;
+
+ quoted = (char *) guc_malloc(ERROR, maxlen * sizeof(char));
+ snprintf(quoted, maxlen, "\'%s\'", buf);
+ guc_free(buf);
+ }
+ else
+ quoted = buf;
+
+ return quoted;
+}
+
+bool
+is_scalar_type(const char *type_name)
+{
+ if (strcmp(type_name, "bool") == 0 ||
+ strcmp(type_name, "int") == 0 ||
+ strcmp(type_name, "real") == 0 ||
+ strcmp(type_name, "string") == 0)
+ return true;
+
+ return false;
+}
+
+static char *
+struct_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ struct type_definition *definition;
+ StringInfoData buf;
+ int cnt_fields;
+ char *result = 0;
+
+ initStringInfo(&buf);
+
+ /* check built-in types */
+ if (is_scalar_type(type))
+ return scalar_to_str(structp, type, writing_to_file);
+
+ /* standard algorithm of preparing structure for writing to file */
+ definition = NULL;
+ if ((definition = get_type_definition(type)) == NULL)
+ return NULL;
+
+ cnt_fields = definition->cnt_fields;
+
+ /* print prefix */
+ appendStringInfo(&buf, "{");
+
+ if (!writing_to_file)
+ appendStringInfo(&buf, "\n");
+
+ /* recurse call for fields */
+ for (int i = 0; i < cnt_fields; i++)
+ {
+ char *field;
+ void *sptr;
+ int offset = get_field_offset(definition->type_name,
+ definition->fields[i].name);
+
+ if (offset < 0)
+ goto out;
+
+ sptr = (char *) structp + offset;
+ field = composite_to_str(sptr, definition->fields[i].type, writing_to_file);
+
+ if (!field)
+ goto out;
+
+ if (writing_to_file)
+ {
+ appendStringInfo(&buf, "%s: %s", definition->fields[i].name, field);
+
+ if (i < cnt_fields - 1)
+ appendStringInfo(&buf, ", ");
+ }
+ else
+ {
+ /* if not write ro file, add tabs at the beginning of each line */
+ char *str_saveptr;
+ char *str_begin = strtok_r(field, "\n", &str_saveptr);
+
+ appendStringInfo(&buf, "\t%s: %s", definition->fields[i].name, str_begin);
+
+ while ((str_begin = strtok_r(NULL, "\n", &str_saveptr)) != NULL)
+ appendStringInfo(&buf, "\n\t%s", str_begin);
+
+ if (i < cnt_fields - 1)
+ appendStringInfo(&buf, ",\n");
+ else
+ appendStringInfo(&buf, "\n");
+ }
+
+ guc_free(field);
+ }
+
+ /* print suffix */
+ appendStringInfo(&buf, "}");
+ result = guc_strdup(ERROR, buf.data);
+
+out:
+ pfree(buf.data);
+
+ return result;
+}
+
+char *
+composite_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ if (is_static_array_type(type))
+ return static_array_to_str(structp, type, writing_to_file);
+
+ if (is_dynamic_array_type(type))
+ return dynamic_array_to_str(structp, type, writing_to_file);
+
+ return struct_to_str(structp, type, writing_to_file);
+}
+
+char *
+normalize_composite_value(const char *option_name, const char *value)
+{
+ /*
+ * Composite value couldn't be wrapped in quotes scalar types must be
+ * escaped and wrapped in quotes All names related to composite values
+ * ended with "->"
+ */
+ bool is_composite = suffix_is_arrow(option_name);
+ char *prepared_val;
+ char *str_val;
+
+ /*
+ * Each value that goes throw this function went throw parser before. If
+ * value is scalar, it was deescaped, else (if value is composite) it
+ * wasn't. Function parse_composite always deescapes scalar values.
+ * Therefore we must escape scalar values for parse_composite
+ */
+ if (!is_composite)
+ {
+ char *escaped = escape_single_quotes_ascii(value);
+ int len = strlen(escaped) + 3;
+
+ /* escape */
+ prepared_val = guc_malloc(ERROR, len);
+ snprintf(prepared_val, len, "\'%s\'", escaped);
+
+ free(escaped);
+ }
+ else
+ prepared_val = (char *) value; /* be careful with free */
+
+ str_val = convert_path_to_composite_value(option_name, prepared_val);
+
+ if (prepared_val != value)
+ guc_free(prepared_val);
+
+ return str_val;
+}
+
+static Size
+get_len_serialized_array(const void *structp, const char *type)
+{
+ char *element_type = get_array_basic_type(type);
+ int total_size = 3;
+ void *datap = NULL;
+ int array_size = 0;
+
+ if (is_dynamic_array_type(type))
+ {
+ array_size = dynamic_array_size(structp);
+ datap = *((void **) structp);
+ }
+ else
+ {
+ array_size = get_static_array_length(type);
+ datap = (void *) structp;
+ }
+
+ /* compute length for first element */
+ for (int i = 0; i < array_size; i++)
+ {
+ int offset = get_element_offset(type, i);
+ int element_len = get_length_composite_str((char *) datap + offset, element_type);
+
+ total_size += element_len + 2; /* 2 = len(", ") */
+ }
+
+ guc_free(element_type);
+
+ return total_size;
+}
+
+static Size
+get_len_serialized_struct(const void *structp, const char *type)
+{
+ struct type_definition *definition = NULL;
+ int total_size = 3;
+
+ if (strcmp(type, "bool") == 0)
+ return 6;
+ else if (strcmp(type, "int") == 0)
+ {
+ if (*(int *) structp < 100)
+ return 4;
+
+ return 12;
+ }
+ else if (strcmp(type, "real") == 0)
+ return 1 + 1 + 1 + REALTYPE_PRECISION + 5;
+ else if (strcmp(type, "string") == 0)
+ {
+ if (*(char **) structp)
+ return strlen(*(char **) structp);
+
+ return 5; /* len of "\nil" */
+ }
+
+ if ((definition = get_type_definition(type)) == NULL)
+ return 0;
+
+ for (int i = 0; i < definition->cnt_fields; i++)
+ {
+ int offset = get_field_offset(definition->type_name, definition->fields[i].name);
+ int field_len = get_length_composite_str((char *) structp + offset, definition->fields[i].type);
+
+ total_size += field_len + 2;
+ }
+
+ return total_size;
+}
+
+Size
+get_length_composite_str(const void *structp, const char *type_name)
+{
+ if (is_static_array_type(type_name) || is_dynamic_array_type(type_name))
+ return get_len_serialized_array(structp, type_name);
+
+ return get_len_serialized_struct(structp, type_name);
+}
+
+char *
+convert_path_to_composite_value(const char *field_path, const char *value)
+{
+ char *path = guc_strdup(ERROR, field_path);
+ char *cur_field = tokenize_field_path(path);
+ char *prefix = guc_strdup(ERROR, "");
+ char *suffix = guc_strdup(ERROR, "");
+ char *result;
+ int len;
+
+ /* skip guc name */
+ cur_field = tokenize_field_path(NULL);
+
+ /* for each step in path generate derived braces and name of field */
+ while (cur_field)
+ {
+ int prefix_len = strlen(prefix);
+ int suffix_len = strlen(suffix);
+
+ int prefix_diff_len = 3 + strlen(cur_field) + 1; /* 3 for "[: ", 1 for
+ * '\0' */
+ int suffix_diff_len = 2;
+
+ char *next_prefix = guc_malloc(ERROR, prefix_len + prefix_diff_len);
+ char *next_suffix = guc_malloc(ERROR, suffix_len + suffix_diff_len);
+
+ snprintf(next_prefix, prefix_len + 1, "%s", prefix);
+ /* define array or structure */
+ if (isdigit(cur_field[0]))
+ {
+ snprintf(next_prefix + prefix_len, 2, "[");
+ snprintf(next_suffix, 2, "]");
+ }
+ else
+ {
+ snprintf(next_prefix + prefix_len, 2, "{");
+ snprintf(next_suffix, 2, "}");
+ }
+
+ snprintf(next_prefix + prefix_len + 1, prefix_diff_len - 1, "%s: ", cur_field);
+ snprintf(next_suffix + 1, suffix_len + 1, "%s", suffix);
+
+ guc_free(prefix);
+ guc_free(suffix);
+
+ prefix = next_prefix;
+ suffix = next_suffix;
+
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ /* construct result from prefix, suffix and value */
+ len = strlen(prefix) + strlen(value) + strlen(suffix) + 1;
+ result = guc_malloc(ERROR, len);
+ snprintf(result, len, "%s%s%s", prefix, value, suffix);
+
+ guc_free(prefix);
+ guc_free(suffix);
+
+ return result;
+}
+
+static void
+static_array_dup(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ const char *basic_type = get_array_basic_type(type_name);
+ int arr_size = get_static_array_length(type_name);
+
+ /* recursive duplicate array elements */
+ for (int i = 0; i < arr_size; i++)
+ {
+ int offset = get_element_offset(type_name, i);
+ void *dest_ptr = (char *) dest_struct + offset;
+ void *src_ptr = (char *) src_struct + offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, basic_type);
+ }
+}
+
+/*
+ * Beware! src_struct points to structure like DynArrTmp
+ */
+static void
+dynamic_array_dup(void *dest_struct, const void *src_struct, const char *type)
+{
+ void *datap;
+ void **dstpp;
+ void *dstp;
+ const char *basic_type = get_array_basic_type(type);
+ int arr_mem_size = get_dynamic_array_size(type, src_struct);
+ int arr_size = dynamic_array_size(src_struct);
+
+ if (!arr_size)
+ {
+ *(void **) dest_struct = NULL;
+ *((void **) dest_struct + 1) = NULL;
+
+ return;
+ }
+
+ datap = *((void **) src_struct);
+ dstpp = (void **) dest_struct;
+ *dstpp = guc_malloc(ERROR, arr_mem_size * sizeof(char));
+ dstp = *dstpp;
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ int offset = get_element_offset(type, i);
+ void *dest_ptr = (char *) dstp + offset;
+ void *src_ptr = (char *) datap + offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, basic_type);
+ }
+
+ dynamic_array_size(dest_struct) = arr_size;
+}
+
+static void
+struct_dup(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return;
+
+ if (is_scalar_type(type_name))
+ {
+ if (!strcmp(type_name, "string"))
+ {
+ if (*(char **) src_struct)
+ *(char **) dest_struct = guc_strdup(ERROR, *(char **) src_struct);
+ else
+ *(char **) dest_struct = NULL;
+
+ return;
+ }
+
+ memcpy(dest_struct, src_struct, struct_type->type_size);
+
+ return;
+ }
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char *field_name = struct_type->fields[i].name;
+ const char *field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type_name, field_name);
+
+ void *dest_ptr = (char *) dest_struct + field_offset;
+ void *src_ptr = (char *) src_struct + field_offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, field_type);
+ }
+}
+
+void
+composite_dup_impl(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ return static_array_dup(dest_struct, src_struct, type_name);
+ if (is_dynamic_array_type(type_name))
+ return dynamic_array_dup(dest_struct, src_struct, type_name);
+
+ return struct_dup(dest_struct, src_struct, type_name);
+}
+
+void *
+composite_dup(const void *structp, const char *type_name)
+{
+ int struct_size;
+ void *duplicate;
+
+ if (!structp)
+ return NULL;
+
+ struct_size = get_composite_size(type_name);
+ duplicate = guc_malloc(ERROR, struct_size);
+
+ /* recursive bypass and searching string */
+ composite_dup_impl(duplicate, structp, type_name);
+
+ return duplicate;
+}
+
+static int
+array_data_cmp(const void *first, const void *second, const char *type, int size)
+{
+ const char *base_type = get_array_basic_type(type);
+ int base_type_size = get_composite_size(base_type);
+ int res = 0;
+
+ /* recursive compare each element */
+ for (int i = 0; i < size; i++)
+ {
+ int offset = base_type_size * i;
+ void *first_element = (char *) first + offset;
+ void *second_element = (char *) second + offset;
+
+ res = composite_cmp(first_element, second_element, base_type);
+
+ if (res)
+ break;
+ }
+
+ return res;
+}
+
+static int
+dynamic_array_cmp(const void *first, const void *second, const char *type)
+{
+ void *first_data = *((void **) first);
+ void *second_data = *((void **) second);
+
+ int first_size = dynamic_array_size(first);
+ int second_size = dynamic_array_size(second);
+
+ int cmp = 0;
+
+ if ((cmp = first_size - second_size))
+ return cmp;
+
+ return array_data_cmp(first_data, second_data, type, first_size);
+}
+
+static int
+struct_cmp(const void *first, const void *second, const char *type_name)
+{
+ int res;
+
+ /* check type */
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return 2; /* error code */
+
+ /* check scalar types like int, real, etc */
+ if (struct_type->cnt_fields == 0)
+ {
+ /* compare string with strcmp, not pointers! */
+ res = 0;
+
+ if (strcmp(type_name, "string") == 0)
+ {
+ if (!*(char **) first && !*(char **) second)
+ return 0;
+
+ if (!*(char **) first)
+ return -1;
+
+ if (!*(char **) second)
+ return 1;
+
+ res = strcmp(*(char **) first, *(char **) second);
+ }
+ else if (strcmp(type_name, "bool") == 0)
+ res = *(bool *) first - *(bool *) second;
+ else if (strcmp(type_name, "int") == 0)
+ res = *(int *) first - *(int *) second;
+ else if (strcmp(type_name, "real") == 0)
+ {
+ double res = *(double *) first - *(double *) second;
+
+ if (res == 0)
+ return 0;
+
+ if (res > 0)
+ return 1;
+
+ return -1;
+ }
+ else
+ return 2;
+
+ if (res == 0)
+ return 0;
+
+ if (res > 0)
+ return 1;
+
+ return -1;
+ }
+
+ /* recursive comparison of fields */
+ res = 0;
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char *field_name = struct_type->fields[i].name;
+ const char *field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type_name, field_name);
+
+ void *first_field = (char *) first + field_offset;
+ void *second_field = (char *) second + field_offset;
+
+ res = composite_cmp(first_field, second_field, field_type);
+
+ if (res)
+ break;
+ }
+
+ return res;
+}
+
+int
+composite_cmp(const void *first, const void *second, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ return array_data_cmp(first, second, type_name, get_static_array_length(type_name));
+
+ if (is_dynamic_array_type(type_name))
+ return dynamic_array_cmp(first, second, type_name);
+
+ return struct_cmp(first, second, type_name);
+}
+
+static void
+free_static_array(void *delptr, const char *type)
+{
+ const char *base_type = get_array_basic_type(type);
+ int arr_size = get_static_array_length(type);
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ void *element_ptr = (char *) delptr + get_element_offset(type, i);
+
+ free_composite_impl(element_ptr, base_type);
+ }
+}
+
+static void
+free_dynamic_array(void *delptr, const char *type)
+{
+ const char *base_type = get_array_basic_type(type);
+ int arr_size = get_static_array_length(type);
+ void **datapp = NULL;
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ void *element_ptr = (char *) delptr + get_element_offset(type, i);
+
+ free_composite_impl(element_ptr, base_type);
+ }
+
+ datapp = (void **) delptr;
+ guc_free(*datapp);
+ *datapp = NULL;
+}
+
+static void
+free_struct(void *delptr, const char *type)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type)) == NULL)
+ return;
+
+ if (struct_type->cnt_fields == 0)
+ {
+ if (strcmp(type, "string") == 0)
+ {
+ char **strp = (char **) delptr;
+
+ guc_free(*strp);
+ *strp = NULL;
+ }
+
+ return;
+ }
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char *field_name = struct_type->fields[i].name;
+ const char *field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type, field_name);
+
+ free_composite_impl((char *) delptr + field_offset, field_type);
+ }
+}
+
+void
+free_composite_impl(void *delptr, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ free_static_array(delptr, type_name);
+
+ if (is_dynamic_array_type(type_name))
+ free_dynamic_array(delptr, type_name);
+
+ free_struct(delptr, type_name);
+}
+
+void
+free_composite(void *delptr, const char *type_name)
+{
+ free_composite_impl(delptr, type_name);
+ guc_free(delptr);
+}
diff --git a/src/backend/utils/misc/guc_composite.h b/src/backend/utils/misc/guc_composite.h
new file mode 100644
index 0000000000..c0ae570438
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite.h
@@ -0,0 +1,84 @@
+/*--------------------------------------------------------------------
+ * guc_composite.h
+ *
+ * Declarations shared between backend/utils/misc/guc.c and
+ * backend/utils/misc/guc_composite.c
+ *
+ * Copyright (c) 2000-2025, PostgreSQL Global Development Group
+ *
+ * src/backend/utils/misc/guc_composite.h
+ *--------------------------------------------------------------------
+ */
+#ifndef GUC_COMPOSITE_H
+#define GUC_COMPOSITE_H
+
+#include "utils/guc.h"
+#include "utils/guc_tables.h"
+#include "utils/hsearch.h"
+
+typedef struct
+{
+ const char *type_name;
+ struct type_definition *definition;
+} OptionTypeHashEntry;
+
+#define IS_STATUS_OK(val) (val.status == PARSER_OK)
+#define IS_STATUS_FAIL(val) (val.status == PARSER_FAIL)
+#define IS_STATUS_ERR(val) (val.status == PARSER_ERR)
+#define IS_STATUS_NOT_FOUND(val) (val.status == PARSER_NOT_FOUND)
+
+/*
+ * Get size in dynamic array. It places after pointer to data
+ */
+#define dynamic_array_size(ptr) (*(int *)((char *)ptr + sizeof(void *)))
+
+#define suffix_is_arrow(name) (name[strlen(name) - 2] == '-' && name[strlen(name) - 1] == '>')
+
+/*
+ * Tokenized path to nest structures. It replaces '->' to '\0' and
+ * returns pointer to first member name.
+ */
+#define tokenize_field_path(path) strtok(path, "->[]")
+
+extern HTAB *guc_types_hashtab;
+
+extern Size get_length_composite_str(const void *structp, const char *type_name);
+extern void init_type_definition(struct type_definition *definition);
+extern struct type_definition *get_type_definition(const char *type_name);
+extern bool is_static_array_type(const char *type_name);
+extern bool is_dynamic_array_type(const char *type_name);
+extern int get_static_array_length(const char *type_name);
+extern int get_array_size(const char *type_name, const int length);
+extern int get_composite_size(const char *type_name);
+extern void composite_dup_impl(void *dest_struct, const void *src_struct, const char *type_name);
+extern void *composite_dup(const void *structp, const char *type_name);
+extern int composite_cmp(const void *first, const void *second, const char *type_name);
+extern char *get_field_type_name(const char *type_name, const char *field);
+extern char *get_nested_field_type_name(const char *type_name, const char *field_path);
+extern int get_field_offset(const char *type_name, const char *field_name);
+extern int get_element_offset(const char *type_name, int index);
+extern void free_composite_impl(void *delptr, const char *type_name);
+extern void free_composite(void *delptr, const char *type_name);
+extern void *get_nested_field_ptr(const void *composite_start, const char *type_name, const char *field_path);
+extern char *normalize_composite_value(const char *option_name, const char *value);
+extern bool parse_composite(const char *strvalue, const char *type, void **result, const void *prev_val, int flags, const char **hintmsg);
+extern char *convert_path_to_composite_value(const char *field_path, const char *value);
+extern char *get_array_basic_type(const char *array_type_name);
+extern bool is_scalar_type(const char *type_name);
+
+/*
+ * Internal functions for parsing guc_composite grammar,
+ * in guc_composite_gram.y and guc_composite_scan.l
+ */
+union YYSTYPE;
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void *yyscan_t;
+#endif
+extern int guc_composite_yyparse(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner);
+extern void guc_composite_yyerror(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner, const char *message);
+extern int guc_composite_yylex(union YYSTYPE *yylval_param, const char **hintmsg, yyscan_t yyscanner);
+extern void guc_composite_scanner_init(const char *str, yyscan_t *yyscannerp);
+extern void guc_composite_scanner_finish(yyscan_t yyscanner);
+
+#endif /* GUC_COMPOSITE_H */
diff --git a/src/backend/utils/misc/guc_composite_gram.y b/src/backend/utils/misc/guc_composite_gram.y
new file mode 100644
index 0000000000..c9a2f52e55
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite_gram.y
@@ -0,0 +1,787 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * guc_composite_gram.y - Parser for all composite guc options
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/pg_list.h"
+#include "utils/guc.h"
+#include "utils/builtins.h"
+#include "guc_composite.h"
+#include "guc_composite_gram.h"
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+extern int guc_composite_yychar;
+extern int guc_composite_yynerrs;
+
+#define context(num) ((parser_ctx *)list_nth(contexts, num))
+#define list_empty(l) (list_length(l) == 0)
+#define last_context (*(parser_ctx **)list_tail(contexts))
+
+#define check_error() do { \
+ if(*hintmsg) \
+ return 1; \
+ } while(0)
+
+/* Stack is needed to memoize valuable data between nested layers in composite object */
+enum name_usage
+{
+ NAME_USAGE_UNKNOWN,
+ NAME_USAGE_ALWAYS,
+ NAME_USAGE_NEVER
+};
+
+/* Stages of setting size for dynamic arrays */
+enum fixed_size
+{
+ FIXED_SIZE_IS_NOT_SETTED, /* Size was not defined for dynamic array */
+ FIXED_SIZE_IS_BEING_SETTED, /* Current value must be setted as a length of
+ * dynamic array */
+ FIXED_SIZE_IS_SETTED /* Indexes in array must be less than size */
+};
+
+typedef struct parser_ctx
+{
+ char *type; /* type of composite object */
+ void *start; /* pointer to start of composite object */
+ int idx; /* automatic computed index for current child */
+ bool has_name; /* name (index) was parsed before current
+ * composite object */
+ bool extended; /* flag for dynamic arrays. True = extended
+ * text representation */
+ enum fixed_size fixed_size; /* field for outer context of dynamic array.
+ * see comments for enum */
+ int max_idx; /* field for outer context of dynamic array.
+ * sets value of max idx in data */
+ enum name_usage name_usage; /* names (indexes) are used for children of
+ * composite object */
+} parser_ctx;
+
+static List *contexts = NIL; /* stack of contexts */
+
+static void init_context(const char *type, void *ptr);
+static void push_context(const char *type, void *start);
+static void free_context(parser_ctx * context);
+static void free_context_list(void);
+static void check_name(yyscan_t yyscanner, const char **hintmsg);
+static void reallocate_dynamic_array(const char *array_type, void *array, int new_len);
+static void check_memory(yyscan_t yyscanner, const char **hintmsg);
+static void prepare_value_context(yyscan_t yyscanner, const char **hintmsg);
+static void prepare_structure(void);
+static void prepare_array(void);
+static void parse_composite_end(void);
+static void parse_field_end(void);
+static int parse_index(const char *index, yyscan_t yyscanner, const char **hintmsg);
+static void parse_element(const char *index, yyscan_t yyscanner, const char **hintmsg);
+static void parse_field(const char *name, yyscan_t yyscanner, const char **hintmsg);
+static void parse_name(const char *name, yyscan_t yyscanner, const char **hintmsg);
+static void parse_scalar_opt(char *strval, const char *struct_type, void *result, int flags, yyscan_t yyscanner, const char **hintmsg);
+%}
+
+%parse-param {void *composite_ptr}
+%parse-param {const char *composite_type}
+%parse-param {const char **hintmsg}
+%parse-param {int flags}
+%parse-param {yyscan_t yyscanner}
+%lex-param {const char **hintmsg}
+%lex-param {yyscan_t yyscanner}
+%pure-parser
+%expect 0
+%name-prefix="guc_composite_yy"
+
+%union
+{
+ char *str;
+}
+
+%token <str> IDENT JUNK
+
+%type placeholder_patch_list
+%type composite
+%type list_or_empty
+%type list
+%type item
+
+%start placeholder_patch_list
+
+%initial-action
+{
+ init_context(composite_type, composite_ptr);
+ (void) yynerrs;
+}
+%%
+
+placeholder_patch_list:
+ composite ';' {
+ init_context(composite_type, composite_ptr);
+ check_error();
+ }
+ placeholder_patch_list
+ | composite
+
+composite:
+ '{' {
+ prepare_value_context(yyscanner, hintmsg);
+ prepare_structure();
+ check_error();
+ }
+ list_or_empty {
+ parse_composite_end();
+ check_error();
+ }
+ '}'
+ | '[' {
+ prepare_value_context(yyscanner, hintmsg);
+ check_error();
+ prepare_array();
+ check_error();
+ }
+ list_or_empty {
+ parse_composite_end();
+ check_error();
+ }
+ ']'
+ | IDENT {
+ prepare_value_context(yyscanner, hintmsg);
+ check_error();
+ parse_scalar_opt($1, context(0)->type, context(0)->start, flags, yyscanner, hintmsg);
+ check_error();
+ parse_composite_end();
+ check_error();
+ }
+ ;
+
+list_or_empty:
+ list
+ | %empty
+ ;
+
+list:
+ item {
+ parse_field_end();
+ check_error();
+ }
+ ',' list
+ | item
+ ;
+
+item:
+ IDENT ':' {
+ parse_name($1, yyscanner, hintmsg);
+ check_error();
+ }
+ composite
+ | composite
+ ;
+%%
+
+
+static void
+init_context(const char *type, void *ptr)
+{
+ free_context_list();
+ push_context(type, ptr);
+}
+
+static void
+push_context(const char *type, void *start)
+{
+ parser_ctx *ctx = palloc(sizeof(parser_ctx));
+
+ if (type)
+ ctx->type = pstrdup(type);
+ else
+ ctx->type = NULL;
+
+ ctx->start = start;
+ ctx->idx = 0;
+ ctx->has_name = false;
+ ctx->extended = false;
+ ctx->fixed_size = FIXED_SIZE_IS_NOT_SETTED;
+ ctx->max_idx = -1;
+ ctx->name_usage = NAME_USAGE_UNKNOWN;
+
+ contexts = lcons(ctx, contexts);
+}
+
+static void
+free_context(parser_ctx * context)
+{
+ if (context->type)
+ pfree(context->type);
+
+ if (context)
+ pfree(context);
+}
+
+static void
+free_context_list(void)
+{
+ while (!list_empty(contexts))
+ {
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+ }
+
+ list_free(contexts);
+}
+
+static void
+check_name(yyscan_t yyscanner, const char **hintmsg)
+{
+ /* Indexes in array are exist either for each element or for no one */
+ if (is_static_array_type(context(1)->type) ||
+ (is_dynamic_array_type(context(1)->type) && context(1)->extended != true))
+ {
+ if (context(0)->has_name)
+ {
+ if (context(1)->name_usage == NAME_USAGE_NEVER)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_ALWAYS;
+ }
+ else
+ {
+ if (context(1)->name_usage == NAME_USAGE_ALWAYS)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_NEVER;
+
+ if (context(1)->start)
+ context(0)->start = (char *) context(1)->start + get_element_offset(context(1)->type, context(1)->idx);
+ }
+ }
+ else if (!context(0)->has_name) /* fields of structures must be labeled
+ * always */
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "all fields in structure must be labeled");
+ return;
+ }
+}
+
+static void
+reallocate_dynamic_array(const char *array_type, void *array, int new_len)
+{
+ int new_arr_mem_size = get_array_size(array_type, new_len);
+ int last_len = dynamic_array_size(array);
+ int last_arr_mem_size = get_array_size(array_type, last_len);
+ int min_arr_mem_size = new_arr_mem_size < last_arr_mem_size ? new_arr_mem_size : last_arr_mem_size;
+ void *new_data = guc_malloc(ERROR, new_arr_mem_size);
+
+ if (new_len < last_len)
+ {
+ /* free elements from deleted part */
+ char *basic_type = get_array_basic_type(array_type);
+
+ for (int i = new_len; i < last_len; i++)
+ {
+ int offset = get_element_offset(array_type, i);
+
+ free_composite_impl((char *) (*(void **) array) + offset, basic_type);
+ }
+
+ guc_free(basic_type);
+ }
+ else
+ memset((char *) new_data + last_arr_mem_size, 0, new_arr_mem_size - last_arr_mem_size);
+
+ memcpy(new_data, *(void **) array, min_arr_mem_size);
+ guc_free(*(void **) array);
+
+ *(void **) array = new_data;
+ dynamic_array_size(array) = new_len;
+}
+
+static void
+check_memory(yyscan_t yyscanner, const char **hintmsg)
+{
+ int len = 0;
+ int idx = 0;
+
+ /*
+ * next part of function process a case, when we expect parsing elements
+ * of data of dynamic array therefore context(1) must be not extended
+ * (i.e. inner context)
+ *
+ * If context(1) is extended dynamic array (i.e. outer context), we are
+ * going to parse "data" or "size" field
+ */
+ if (!(is_dynamic_array_type(context(1)->type)
+ && context(1)->extended != true))
+ return;
+
+ len = dynamic_array_size(context(2)->start);
+ idx = context(1)->idx;
+
+ if (idx > context(2)->max_idx)
+ context(2)->max_idx = idx;
+
+ if (idx >= len)
+ {
+ int offset;
+
+ if (context(2)->fixed_size == FIXED_SIZE_IS_SETTED)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "there is index greater than array length.\
+ Change \"size\" field");
+ return;
+ }
+
+ reallocate_dynamic_array(context(2)->type, context(2)->start, idx + 1);
+
+ /* update contexts about dynamic array */
+ context(1)->start = *(void **) context(2)->start;
+ offset = get_element_offset(context(1)->type, context(1)->idx);
+ context(0)->start = (char *) context(1)->start + offset;
+ }
+}
+
+/*
+ * Check all conditions related to context: name, allocated memory
+ */
+static void
+prepare_value_context(yyscan_t yyscanner, const char **hintmsg)
+{
+ /* top-level structure always is okey */
+ if (list_length(contexts) == 1)
+ return;
+ check_name(yyscanner, hintmsg);
+ check_memory(yyscanner, hintmsg);
+}
+
+/*
+ * Prepare context for structure. Add new layer in stack that will be filled by parse_name
+ */
+static void
+prepare_structure(void)
+{
+ /* if type is dynamic array => that is extended form of array */
+ if (is_dynamic_array_type(context(0)->type))
+ context(0)->extended = true;
+
+ /* add context for structure field */
+ push_context(NULL, context(0)->start);
+}
+
+/*
+ * Prepare context for array. Add new layer to stack that will be filled by parse_name.
+ * In case of parsing dynamic array we must guarantee that
+ * current context->start points to pointer to data of dynamic array
+ */
+static void
+prepare_array(void)
+{
+ void *data = NULL;
+ char *basic_type = get_array_basic_type(context(0)->type);
+
+ if (!is_dynamic_array_type(context(0)->type))
+ {
+ push_context(pstrdup(basic_type), context(0)->start);
+
+ goto out;
+ }
+ else
+ {
+ /*
+ * There are 4 cases that could be: "{data: [" or "[" or "[ [" or "{
+ * field: [" and only first case don't need creating new stack layer
+ */
+ if (!(list_length(contexts) > 1 && context(1)->extended))
+ {
+ data = *(void **) context(0)->start;
+
+ /*
+ * Each dynamic array has 2 contexts: outer context for structure
+ * {data, size} And inner context for allocated data. Because of
+ * different ways to set dynamic array (extended and compact
+ * forms) outer context of dynamic array has flag "extended". When
+ * we pop from stack, we see this flag and for compact
+ * representation of array we pop twice (for purpose of popping
+ * inner and outer contexts at one time)
+ */
+ if (data)
+ push_context(context(0)->type, data);
+ else
+ push_context(context(0)->type, NULL);
+ }
+
+ data = context(0)->start;
+
+ /*
+ * Current dynamic array could be empty. In this case create
+ * fictitious stack layer (NULL in start field means that now we parse
+ * element of empty dynamic array) for purposes of recursive algorithm
+ */
+ if (data)
+ push_context(pstrdup(basic_type), data);
+ else
+ push_context(pstrdup(basic_type), NULL);
+ }
+
+out:
+ guc_free(basic_type);
+}
+
+/*
+ * Update context when parser go out of nested
+ * level of structure. So, rewind stack of contexts.
+ */
+static void
+parse_composite_end(void)
+{
+ /*
+ * is_dynamic is used to not miss in case: Delete context and view not
+ * extended dynamic array. So that is inner or outer context? If
+ * is_dynamic == true => that is outer context Else that is inner context
+ */
+ bool is_dynamic = false;
+
+ /*
+ * is_extended is used in cases when we delete outer context of array that
+ * is nested in dynamic array in compact form
+ */
+ bool is_extended = context(0)->extended;
+
+ /*
+ * type of current context might have NULL type in case than we parse {}
+ * Then go exactly to deleting context
+ */
+ if (context(0)->type)
+ is_dynamic = is_dynamic_array_type(context(0)->type);
+
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+
+ /*
+ * If deleted layer was extended, that layer was outer context => return
+ */
+ if (is_extended)
+ return;
+
+ /*
+ * Free up outer context for dynamic array in compact form. See comments
+ * in parse_array function
+ */
+ if (!list_empty(contexts)
+ && is_dynamic
+ && is_dynamic_array_type(context(0)->type)
+ && context(0)->extended != true)
+ {
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+ }
+}
+
+/*
+ * Update context before parser go to parse next field
+ */
+static void
+parse_field_end(void)
+{
+ context(0)->idx++; /* context of structure/array */
+
+ if (is_dynamic_array_type(context(0)->type) || is_static_array_type(context(0)->type))
+ {
+ void *data = *(void **) context(0)->start;
+ char *basic_type = get_array_basic_type(context(0)->type);
+
+ if (data)
+ push_context(pstrdup(basic_type), data);
+ else
+ push_context(pstrdup(basic_type), NULL);
+
+ guc_free(basic_type);
+ }
+ else
+ push_context(NULL, context(0)->start);
+
+ context(0)->has_name = false; /* context of field/element */
+}
+
+static int
+parse_index(const char *index, yyscan_t yyscanner, const char **hintmsg)
+{
+ for (const char *c = index; *c; c++)
+ {
+ if (!isdigit(*c))
+ {
+ guc_composite_yyerror(last_context->start, last_context->type, hintmsg, 0, yyscanner, "incorrect index");
+ return -1;
+ }
+ }
+
+ return atoi(index);
+}
+
+/*
+ * Parse index and compute offset in array. Fill current context
+ */
+static void
+parse_element(const char *index, yyscan_t yyscanner, const char **hintmsg)
+{
+ int idx = parse_index(index, yyscanner, hintmsg);
+
+ if (*hintmsg)
+ return;
+
+ /* for static array check len */
+ if (is_static_array_type(context(1)->type))
+ {
+ int len = get_static_array_length(context(1)->type);
+
+ if (idx >= len)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "there is index which is greater than size of array");
+ return;
+ }
+ }
+
+ context(1)->idx = idx;
+ context(0)->start = (char *) context(1)->start + get_element_offset(context(1)->type, idx);
+}
+
+/*
+ * Parse name, check correctness. Fill current context
+ */
+static void
+parse_field(const char *name, yyscan_t yyscanner, const char **hintmsg)
+{
+ char *field_type;
+ int offset = get_field_offset(context(1)->type, name);
+
+ if (offset < 0)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "incorrect field name");
+ return;
+ }
+
+ /* create new context */
+ field_type = get_field_type_name(context(1)->type, name);
+ context(0)->type = pstrdup(field_type);
+ context(0)->start = (char *) context(1)->start + offset;
+ guc_free(field_type);
+
+ /* process fields size and data in extended version of dynamic array */
+ if (context(1)->extended)
+ {
+ if (strcmp(name, "size") == 0)
+ context(1)->fixed_size = FIXED_SIZE_IS_BEING_SETTED;
+ else if (strcmp(name, "data") == 0)
+ {
+ void *data = *(void **) context(1)->start;
+
+ if (data)
+ context(0)->start = data;
+ else
+ context(0)->start = NULL;
+ }
+ }
+}
+
+/*
+ * Compute the pointer to field by name of field, fill current context
+ */
+static void
+parse_name(const char *name, yyscan_t yyscanner, const char **hintmsg)
+{
+ /* Name could be an index for arrays */
+ if (is_static_array_type(context(1)->type) ||
+ (is_dynamic_array_type(context(1)->type) && context(1)->extended == false))
+ {
+ if (context(1)->name_usage == NAME_USAGE_NEVER)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_ALWAYS;
+ parse_element(name, yyscanner, hintmsg);
+ }
+ else
+ parse_field(name, yyscanner, hintmsg);
+
+ context(0)->has_name = true;
+}
+
+static void
+parse_scalar_opt(char *strval, const char *struct_type, void *result, int flags, yyscan_t yyscanner, const char **hintmsg)
+{
+ if (strcmp(struct_type, "bool") == 0)
+ {
+ if (!parse_bool(strval, (bool *) result))
+ {
+ guc_composite_yyerror(last_context->start, last_context->type, hintmsg, 0, yyscanner, "failed to parse bool value, use 'on' and 'off'");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "int") == 0)
+ {
+ /*
+ * Block of code for field "size" in dynamic array This block of code
+ * must be above parse_int(). Because standard parsing will overwrite
+ * old length, but we need it in reallocate_dynamic_array()
+ */
+ if (list_length(contexts) > 1 && context(1)->fixed_size == FIXED_SIZE_IS_BEING_SETTED)
+ {
+ int len = parse_index(strval, yyscanner, hintmsg);
+
+ if (*hintmsg)
+ return;
+
+ if (len <= context(1)->max_idx)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "fixed size of dynamic array less\
+ or eaual maximum index");
+ return;
+ }
+
+ reallocate_dynamic_array(context(1)->type, context(1)->start, len);
+ context(1)->fixed_size = FIXED_SIZE_IS_SETTED;
+ }
+
+ if (!parse_int(strval, (int *) result, flags, hintmsg))
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to parse int value, check units");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "real") == 0)
+ {
+ if (!parse_real(strval, (double *) result, flags, hintmsg))
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to parse real value, check delimiter");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "string") == 0)
+ {
+ if (strcmp(strval, "\\nil") == 0)
+ *((char **) result) = NULL;
+ else
+ {
+ if (*((char **) result))
+ guc_free(*((char **) result));
+
+ *((char **) result) = guc_strdup(ERROR, strval);
+ }
+ }
+ else
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to determine type of simple field");
+ return;
+ }
+}
+
+bool
+parse_composite(const char *strvalue, const char *type, void **result, const void *prev_val, int flags, const char **hintmsg)
+{
+ int size = 0;
+ yyscan_t scanner;
+ void *val = NULL;
+ int parser_result = 0;
+ bool check = true;
+
+ *hintmsg = NULL;
+ size = get_composite_size(type);
+ val = guc_malloc(ERROR, size);
+
+ if (prev_val)
+ composite_dup_impl(val, prev_val, type);
+ else
+ memset(val, 0, size);
+
+ guc_composite_scanner_init(strvalue, &scanner);
+ parser_result = guc_composite_yyparse(val, type, hintmsg, flags, scanner);
+ guc_composite_scanner_finish(scanner);
+ free_context_list();
+ if (parser_result != 0 || *hintmsg)
+ {
+ guc_free(val);
+ *result = NULL;
+ check = false;
+ }
+ else
+ *result = val;
+
+ return check;
+}
diff --git a/src/backend/utils/misc/guc_composite_scan.l b/src/backend/utils/misc/guc_composite_scan.l
new file mode 100644
index 0000000000..1f249ea87a
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite_scan.l
@@ -0,0 +1,132 @@
+%top{
+/*-------------------------------------------------------------------------
+ *
+ * guc_composite_scan.l
+ * a lexical scanner for all composite guc values
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite_scan.l
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "utils/guc.h"
+#include "guc_composite.h"
+#include "guc_composite_gram.h"
+
+#define YY_DECL extern int guc_composite_yylex(union YYSTYPE *yylval_param, const char **hintmsg, yyscan_t yyscanner)
+}
+%option reentrant
+%option bison-bridge
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option noyyalloc
+%option noyyrealloc
+%option noyyfree
+%option warn
+%option prefix="guc_composite_yy"
+
+SPACE [ \t\n\r\f\v]
+
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+STRING \'([^'\\\n]|\\.|\'\')*\'
+UNQUOTED_STRING ({LETTER_OR_DIGIT}|\.)*
+
+
+%%
+{SPACE} /* Eat whitespace */
+
+{STRING} {
+ /* Deescape string and return value */
+ yylval->str = DeescapeQuotedString(yytext);
+ return IDENT;
+ }
+
+{UNQUOTED_STRING} {
+ yylval->str = pstrdup(yytext);
+ return IDENT;
+ }
+
+"{" { return '{'; }
+"}" { return '}'; }
+"[" { return '['; }
+"]" { return ']'; }
+"," { return ','; }
+":" { return ':'; }
+";" { return ';'; }
+
+. {
+ guc_composite_yyerror(NULL, NULL, hintmsg, 0, yyscanner, "illegible lexeme");
+ return JUNK;
+ }
+%%
+
+/* functions for lexer */
+
+void
+guc_composite_scanner_init(const char *str, yyscan_t *yyscannerp)
+{
+ yyscan_t yyscanner;
+
+ if (yylex_init(yyscannerp) != 0)
+ elog(ERROR, "yylex_init() failed: %m");
+ yyscanner = *yyscannerp;
+ yy_scan_string(str, yyscanner);
+}
+
+void
+guc_composite_scanner_finish(yyscan_t yyscanner)
+{
+ yylex_destroy(yyscanner);
+}
+
+void
+guc_composite_yyerror(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner, const char *message)
+{
+ struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; /* needed for yytext
+ * macro */
+
+ /* report only the first error in a parse operation */
+ if (*hintmsg)
+ return;
+ if (yytext[0])
+ *hintmsg = psprintf("%s at or near \"%s\"", message, yytext);
+ else
+ *hintmsg = psprintf("%s at the end of input", message);
+}
+
+/*
+ * Interface functions to make flex use palloc() instead of malloc().
+ * It'd be better to make these static, but flex insists otherwise.
+ */
+
+void *
+yyalloc(yy_size_t size, yyscan_t yyscanner)
+{
+ return palloc(size);
+}
+
+void *
+yyrealloc(void *ptr, yy_size_t size, yyscan_t yyscanner)
+{
+ if (ptr)
+ return repalloc(ptr, size);
+ else
+ return palloc(size);
+}
+
+void
+yyfree(void *ptr, yyscan_t yyscanner)
+{
+ if (ptr)
+ pfree(ptr);
+}
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index b9e26982ab..a724fec0fc 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -18,6 +18,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "utils/guc.h"
#include "access/xact.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
@@ -55,15 +56,26 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
switch (stmt->kind)
{
+ char *prepared_name;
case VAR_SET_VALUE:
case VAR_SET_CURRENT:
+ if (stmt_has_serialized_composite(stmt))
+ {
+ int name_len = strlen(stmt->name) + 3;
+ prepared_name = guc_malloc(ERROR, name_len);
+ snprintf(prepared_name, name_len, "%s->", stmt->name);
+ }
+ else
+ prepared_name = guc_strdup(ERROR, stmt->name);
+
if (stmt->is_local)
WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
- (void) set_config_option(stmt->name,
+ (void) set_config_option(prepared_name,
ExtractSetVariableArgs(stmt),
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
action, true, 0, false);
+ guc_free(prepared_name);
break;
case VAR_SET_MULTI:
@@ -157,6 +169,42 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
ACL_SET, stmt->kind, false);
}
+/*
+ * Get arguments of set statement and check if some arguments are
+ * serialized composite options
+ */
+bool
+stmt_has_serialized_composite(VariableSetStmt *stmt)
+{
+ List *args;
+ ListCell *l;
+
+ switch (stmt->kind)
+ {
+ case VAR_SET_VALUE:
+ args = stmt->args;
+ /* Fast path if just DEFAULT */
+ if (args == NIL)
+ return false;
+ /* go throw args list and check nodeTags */
+ foreach(l, args)
+ {
+ A_Const *con;
+ Node *arg = (Node *) lfirst(l);
+
+ if (!IsA(arg, A_Const))
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ con = (A_Const *) arg;
+
+ if (nodeTag(&con->val) == T_SerializedComposite)
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
/*
* Get the value to assign for a VariableSetStmt, or NULL if it's RESET.
* The result is palloc'd.
@@ -295,6 +343,10 @@ flatten_set_variable_args(const char *name, List *args)
appendStringInfoString(&buf, val);
}
break;
+ case T_SerializedComposite:
+ val = compositeVal(&con->val);
+ appendStringInfoString(&buf, val);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(&con->val));
@@ -618,8 +670,6 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
/* context */
values[6] = GucContext_Names[conf->context];
- /* vartype */
- values[7] = config_type_names[conf->vartype];
/* source */
values[8] = GucSource_Names[conf->source];
@@ -631,6 +681,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_bool *lconf = (struct config_bool *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -652,6 +705,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_int *lconf = (struct config_int *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
snprintf(buffer, sizeof(buffer), "%d", lconf->min);
values[9] = pstrdup(buffer);
@@ -677,6 +733,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_real *lconf = (struct config_real *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
snprintf(buffer, sizeof(buffer), "%g", lconf->min);
values[9] = pstrdup(buffer);
@@ -702,6 +761,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_string *lconf = (struct config_string *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -729,6 +791,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_enum *lconf = (struct config_enum *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -754,6 +819,48 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *lconf = (struct config_composite *) conf;
+ int len = (strlen(lconf->type_name) + 8);
+
+ /* vartype */
+ values[7] = palloc(len);
+ snprintf((char *) values[7], len, "%s %s", config_type_names[conf->vartype], lconf->type_name);
+
+ /* min_val */
+ values[9] = NULL;
+
+ /* max_val */
+ values[10] = NULL;
+
+ /* enumvals */
+ values[11] = NULL;
+ /* boot_val */
+ if (lconf->boot_val == NULL)
+ values[12] = NULL;
+ else
+ {
+ bool not_write_to_file = false;
+ char *boot_v = composite_to_str(lconf->boot_val, lconf->type_name, not_write_to_file);
+
+ values[12] = pstrdup(boot_v);
+ guc_free(boot_v);
+ }
+ /* reset_val */
+ if (lconf->reset_val == NULL)
+ values[13] = NULL;
+ else
+ {
+ bool not_write_to_file = false;
+ char *reset = composite_to_str(lconf->reset_val, lconf->type_name, not_write_to_file);
+
+ values[13] = pstrdup(reset);
+ guc_free(reset);
+ }
+ }
+ break;
+
default:
{
/*
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 6bc6be13d2..c392d00972 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -917,6 +917,12 @@
boot_val => 'true',
},
+{ name => 'extended_guc_arrays', type => 'bool', context => 'PGC_USERSET', group => 'CUSTOM_OPTIONS',
+ short_desc => 'Enables expanded view of dynamic arrays in GUC',
+ variable => 'extended_array_view',
+ boot_val => 'false',
+},
+
{ name => 'archive_timeout', type => 'int', context => 'PGC_SIGHUP', group => 'WAL_ARCHIVING',
short_desc => 'Sets the amount of time to wait before forcing a switch to the next WAL file.',
long_desc => '0 disables the timeout.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 00c8376cf4..c45be54f3e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -755,10 +755,72 @@ const char *const config_type_names[] =
[PGC_REAL] = "real",
[PGC_STRING] = "string",
[PGC_ENUM] = "enum",
+ [PGC_COMPOSITE] = "composite",
};
-StaticAssertDecl(lengthof(config_type_names) == (PGC_ENUM + 1),
+StaticAssertDecl(lengthof(config_type_names) == (PGC_COMPOSITE + 1),
"array length mismatch");
+/*
+ * Table of user composite types
+ *
+ * See src/backend/utils/misc/README for design notes.
+ *
+ * TO ADD NEW DEFINITION OF COMPOSITE TYPE:
+ *
+ * 1. Decide on a type name
+ *
+ * 2. Add a record below.
+ * Signature (second field of structure) must be in format:
+ * "<type_1> <name_1>;<type_2> <name_2>; ... ; <type_K> <name_K>"
+ * where type_N - one of { bool, int, real, string, <custom_type>}
+ * (custom_type - already defined composite type,
+ * array[<number>] - static array,
+ * array[] - dynamic array)
+ *
+ * HINT. Do not fill 3 - 6 fields, they will be filled automatically
+ * in init_type_definition function
+ *
+ * NOTE. You must change set of functions in guc_composite.c to add
+ * a scalar type. See how built-in types are implemented.
+ */
+struct type_definition UserDefinedConfigureTypes[] = {
+ {
+ "bool",
+ NULL,
+ 0,
+ sizeof(bool),
+ sizeof(bool),
+ NULL
+ },
+ {
+ "int",
+ NULL,
+ 0,
+ sizeof(int),
+ sizeof(int),
+ NULL
+ },
+ {
+ "real",
+ NULL,
+ 0,
+ sizeof(double),
+ sizeof(double),
+ NULL
+ },
+ {
+ "string",
+ NULL,
+ 0,
+ sizeof(char *),
+ sizeof(char *),
+ NULL
+ },
+ /* End-of-list marker */
+ {
+ NULL, NULL
+ }
+};
#include "utils/guc_tables.inc.c"
diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c
index 55c36ddf05..2722baa262 100644
--- a/src/backend/utils/misc/help_config.c
+++ b/src/backend/utils/misc/help_config.c
@@ -35,6 +35,7 @@ typedef union
struct config_int integer;
struct config_string string;
struct config_enum _enum;
+ struct config_composite _composite;
} mixedStruct;
@@ -125,6 +126,18 @@ printMixedStruct(mixedStruct *structToPrint)
structToPrint->_enum.boot_val));
break;
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ char *valstr = NULL;
+
+ valstr = composite_to_str(structToPrint->_composite.boot_val,
+ structToPrint->_composite.type_name,
+ not_write_to_file);
+ printf("COMPSITE %s\t%s\t\t\t", structToPrint->_composite.type_name, valstr);
+ guc_free(valstr);
+ break;
+ }
default:
write_stderr("internal error: unrecognized run-time parameter type\n");
break;
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 9e389a00d0..3a9695af60 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -2,6 +2,7 @@
backend_sources += files(
'conffiles.c',
+ 'guc_composite.c',
'guc.c',
'guc_funcs.c',
'guc_tables.c',
@@ -20,6 +21,31 @@ backend_sources += files(
'tzparser.c',
)
+# see ../../parser/meson.build
+guc_composite_parser_sources = []
+
+guc_composite_scan = custom_target('guc_composite_scan',
+ input: 'guc_composite_scan.l',
+ output: 'guc_composite_scan.c',
+ command: flex_cmd,
+)
+generated_sources += guc_composite_scan
+guc_composite_parser_sources += guc_composite_scan
+
+guc_composite_gram = custom_target('guc_composite_gram',
+ input: 'guc_composite_gram.y',
+ kwargs: bison_kw,
+)
+generated_sources += guc_composite_gram.to_list()
+guc_composite_parser_sources += guc_composite_gram
+
+backend_link_with += static_library('guc_composite_parser',
+ guc_composite_parser_sources,
+ dependencies: [backend_code],
+ include_directories: include_directories('.'),
+ kwargs: internal_lib_args,
+)
+
guc_scan = custom_target('guc_scan',
input: 'guc-file.l',
output: 'guc-file.c',
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a28..5e5552b3c7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -102,6 +102,7 @@ extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid,
bool summarizing, bool withoutoverlaps);
extern Node *makeStringConst(char *str, int location);
+extern Node *makeSerializedCompositeConst(char *str, int location);
extern DefElem *makeDefElem(char *name, Node *arg, int location);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction, int location);
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index 3ee3b976b8..6a1b31c90f 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -76,15 +76,25 @@ typedef struct BitString
char *bsval;
} BitString;
+typedef struct SerializedComposite
+{
+ pg_node_attr(special_read_write)
+
+ NodeTag type;
+ char *sval;
+} SerializedComposite;
+
#define intVal(v) (castNode(Integer, v)->ival)
#define floatVal(v) atof(castNode(Float, v)->fval)
#define boolVal(v) (castNode(Boolean, v)->boolval)
#define strVal(v) (castNode(String, v)->sval)
+#define compositeVal(v) (castNode(SerializedComposite, v)->sval)
extern Integer *makeInteger(int i);
extern Float *makeFloat(char *numericStr);
extern Boolean *makeBoolean(bool val);
extern String *makeString(char *str);
extern BitString *makeBitString(char *str);
+extern SerializedComposite *makeSerializedComposite(char *str);
#endif /* VALUE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da8..b197a81f28 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -185,12 +185,14 @@ typedef bool (*GucIntCheckHook) (int *newval, void **extra, GucSource source);
typedef bool (*GucRealCheckHook) (double *newval, void **extra, GucSource source);
typedef bool (*GucStringCheckHook) (char **newval, void **extra, GucSource source);
typedef bool (*GucEnumCheckHook) (int *newval, void **extra, GucSource source);
+typedef bool (*GucCompositeCheckHook) (void *newval, void **extra, GucSource source);
typedef void (*GucBoolAssignHook) (bool newval, void *extra);
typedef void (*GucIntAssignHook) (int newval, void *extra);
typedef void (*GucRealAssignHook) (double newval, void *extra);
typedef void (*GucStringAssignHook) (const char *newval, void *extra);
typedef void (*GucEnumAssignHook) (int newval, void *extra);
+typedef void (*GucCompositeAssignHook) (void *newval, void *extra);
typedef const char *(*GucShowHook) (void);
@@ -243,6 +245,11 @@ typedef enum
#define GUC_UNIT (GUC_UNIT_MEMORY | GUC_UNIT_TIME)
+/*
+ * Precision with which REAL type guc values are to be printed for GUC
+ * serialization.
+ */
+#define REALTYPE_PRECISION 17
/* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */
extern PGDLLIMPORT bool Debug_print_plan;
@@ -325,6 +332,8 @@ extern PGDLLIMPORT char *role_string;
extern PGDLLIMPORT bool in_hot_standby_guc;
extern PGDLLIMPORT bool trace_sort;
+extern PGDLLIMPORT bool extended_array_view;
+
#ifdef DEBUG_BOUNDED_SORT
extern PGDLLIMPORT bool optimize_bounded_sort;
#endif
@@ -413,6 +422,20 @@ extern void DefineCustomEnumVariable(const char *name,
GucEnumAssignHook assign_hook,
GucShowHook show_hook) pg_attribute_nonnull(1, 4);
+extern void DefineCustomCompositeVariable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ const char *type_name,
+ void *valueAddr,
+ const void *bootValueAddr,
+ GucContext context,
+ int flags,
+ GucCompositeCheckHook check_hook,
+ GucCompositeAssignHook assign_hook,
+ GucShowHook show_hook);
+
+extern void DefineCustomCompositeType(const char *type_name, const char *signature);
+
extern void MarkGUCPrefixReserved(const char *className);
/* old name for MarkGUCPrefixReserved, for backwards compatibility: */
@@ -473,6 +496,8 @@ pg_nodiscard extern void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+char *composite_to_str(const void *structp, const char *type, bool writing_to_file);
+
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
extern void read_nondefault_variables(void);
@@ -486,6 +511,7 @@ extern void RestoreGUCState(void *gucstate);
/* Functions exported by guc_funcs.c */
extern void ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel);
extern char *ExtractSetVariableArgs(VariableSetStmt *stmt);
+extern bool stmt_has_serialized_composite(VariableSetStmt *stmt);
extern void SetPGVariable(const char *name, List *args, bool is_local);
extern void GetPGVariable(const char *name, DestReceiver *dest);
extern TupleDesc GetPGVariableResultDesc(const char *name);
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index f72ce944d7..efe85fd80f 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -27,6 +27,7 @@ enum config_type
PGC_REAL,
PGC_STRING,
PGC_ENUM,
+ PGC_COMPOSITE,
};
union config_var_val
@@ -36,6 +37,7 @@ union config_var_val
double realval;
char *stringval;
int enumval;
+ void *compositeval;
};
/*
@@ -298,18 +300,56 @@ struct config_enum
void *reset_extra;
};
+/* work with type signatures */
+
+typedef struct struct_field
+{
+ char *type;
+ char *name;
+} struct_field;
+
+struct type_definition
+{
+ /* constant fields */
+ const char *type_name;
+ const char *signature;
+ int cnt_fields;
+ int type_size;
+ int offset;
+ struct_field *fields;
+};
+
+struct config_composite
+{
+ struct config_generic gen;
+ /* constant fields, must be set correctly in initial value: */
+ const char *type_name;
+ void *variable;
+ const void *boot_val;
+ GucCompositeCheckHook check_hook;
+ GucCompositeAssignHook assign_hook;
+ GucShowHook show_hook;
+ /* variable fields, initialized at runtime: */
+ void *reset_val;
+ void *reset_extra;
+ const struct type_definition *definition;
+};
+
/* constant tables corresponding to enums above and in guc.h */
extern PGDLLIMPORT const char *const config_group_names[];
extern PGDLLIMPORT const char *const config_type_names[];
extern PGDLLIMPORT const char *const GucContext_Names[];
extern PGDLLIMPORT const char *const GucSource_Names[];
+/* array defining all the built-in composite types for GUC variables */
+extern PGDLLIMPORT struct type_definition UserDefinedConfigureTypes[];
/* data arrays defining all the built-in GUC variables */
extern PGDLLIMPORT struct config_bool ConfigureNamesBool[];
extern PGDLLIMPORT struct config_int ConfigureNamesInt[];
extern PGDLLIMPORT struct config_real ConfigureNamesReal[];
extern PGDLLIMPORT struct config_string ConfigureNamesString[];
extern PGDLLIMPORT struct config_enum ConfigureNamesEnum[];
+extern PGDLLIMPORT struct config_composite ConfigureNamesComposite[];
/* lookup GUC variables, returning config_generic pointers */
extern struct config_generic *find_option(const char *name,
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index f22ca213c2..3608453e6d 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -260,6 +260,10 @@ sub main
s/{/ { /g;
s/}/ } /g;
+ # Make exclusion for '{' and '}'
+ s/' \{ '/'{'/g;
+ s/' \} '/'}'/g;
+
# Likewise for comment start/end markers
s|\/\*| /* |g;
s|\*\/| */ |g;
--
2.48.1
guc_composite_types_tests.patchtext/x-patchDownload
From 088520d9550a5dbb98c91381d1a489f02839d64d Mon Sep 17 00:00:00 2001
From: Anton Chumak <A.M.Chumak@yandex.com>
Date: Mon, 8 Sep 2025 10:10:10 +0700
Subject: [PATCH] added guc option for test purposes
The test_composite option has been added and the corresponding global variable
testComposite of type struct TestComposite has been added to the guc.h and
guc.c files. This option contains all possible atomic data types, it also
contains static and dynamic arrays. Using her example, you can understand how
to work with composite options and test the behavior of the new GUC module.
---
contrib/test_guc/Makefile | 18 ++
contrib/test_guc/test_guc--1.0.sql | 0
contrib/test_guc/test_guc.c | 119 ++++++++++
contrib/test_guc/test_guc.control | 5 +
src/backend/replication/slot.c | 3 +
src/backend/replication/syncrep.c | 3 +
src/backend/utils/error/elog.c | 2 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 1 -
src/backend/utils/misc/guc_parameters.dat | 28 +++
src/backend/utils/misc/guc_tables.c | 8 +
src/backend/utils/misc/postgresql.conf.sample | 5 +
src/include/miscadmin.h | 9 +
src/include/replication/slot.h | 10 +
src/include/replication/syncrep.h | 15 ++
src/include/utils/elog.h | 2 +
src/include/utils/guc.h | 1 +
src/test/modules/test_misc/meson.build | 1 +
.../test_misc/t/009_test_guc_composite.pl | 49 +++++
src/test/regress/expected/guc_composite.out | 203 ++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/guc_composite.sql | 55 +++++
22 files changed, 540 insertions(+), 2 deletions(-)
create mode 100644 contrib/test_guc/Makefile
create mode 100644 contrib/test_guc/test_guc--1.0.sql
create mode 100644 contrib/test_guc/test_guc.c
create mode 100644 contrib/test_guc/test_guc.control
create mode 100644 src/test/modules/test_misc/t/009_test_guc_composite.pl
create mode 100644 src/test/regress/expected/guc_composite.out
create mode 100644 src/test/regress/sql/guc_composite.sql
diff --git a/contrib/test_guc/Makefile b/contrib/test_guc/Makefile
new file mode 100644
index 0000000000..6e844776c6
--- /dev/null
+++ b/contrib/test_guc/Makefile
@@ -0,0 +1,18 @@
+# contrib/test_guc/Makefile
+MODULE_big = test_guc
+OBJS = \
+ $(WIN32RES) \
+ test_guc.o
+EXTENSION = test_guc
+DATA = test_guc--1.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/test_guc
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
\ No newline at end of file
diff --git a/contrib/test_guc/test_guc--1.0.sql b/contrib/test_guc/test_guc--1.0.sql
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contrib/test_guc/test_guc.c b/contrib/test_guc/test_guc.c
new file mode 100644
index 0000000000..66d18b47e5
--- /dev/null
+++ b/contrib/test_guc/test_guc.c
@@ -0,0 +1,119 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/guc.h"
+#include "utils/guc_tables.h"
+#include "utils/elog.h"
+#include "string.h"
+
+PG_MODULE_MAGIC;
+
+struct LogLevel
+{
+ char *name;
+ int level;
+};
+
+struct LogLevel logLevelsMapping[5];
+struct LogLevel logLevelsMappingBoot[5];
+
+
+struct NodeConfig
+{
+ char *name;
+ char *ip;
+ int port;
+ int max_connections;
+ bool enable_connections;
+};
+
+struct NodeArray
+{
+ struct NodeConfig *data;
+ int size;
+};
+
+struct ClusterConfig
+{
+ char *name;
+ char *leader;
+ struct NodeArray nodes;
+};
+
+struct ClusterConfig clusterConfig;
+struct ClusterConfig clusterConfigBoot;
+
+
+bool validateCluster(void *newValue, void **extra, GucSource source);
+
+void _PG_init(void);
+
+void
+_PG_init(void)
+{
+ DefineCustomCompositeType("ext.loglevelmap", "string name; int level");
+
+ DefineCustomCompositeVariable("ext.loglevels",
+ "Mapping between level names and values",
+ NULL,
+ "ext.loglevelmap[5]",
+ &logLevelsMapping,
+ &logLevelsMappingBoot,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+
+
+ DefineCustomCompositeType("ext.nodeConfig", "string name; string ip; int port; int max_connections; bool enable_connections");
+ DefineCustomCompositeType("ext.clusterConfig", "string name; string leader; ext.nodeConfig[] nodes");
+
+ DefineCustomCompositeVariable("ext.cluster",
+ "Example of multi-level structure",
+ NULL,
+ "ext.clusterConfig",
+ &clusterConfig,
+ &clusterConfigBoot,
+ PGC_USERSET,
+ 0,
+ &validateCluster,
+ NULL,
+ NULL);
+
+
+ MarkGUCPrefixReserved("ext");
+}
+
+
+bool
+validateCluster(void *newValueRaw, void **extra, GucSource source)
+{
+ struct ClusterConfig *newValue = (struct ClusterConfig *)newValueRaw;
+ bool found_leader = false;
+ int max_connections = 0;
+
+ if (newValue->name == NULL)
+ return true;
+
+ for (int i = 0; i < newValue->nodes.size; i++)
+ {
+ if (strcmp(newValue->leader, newValue->nodes.data[i].name) == 0)
+ {
+ found_leader = true;
+ max_connections = newValue->nodes.data[i].max_connections;
+ break;
+ }
+ }
+
+ if (!found_leader)
+ return false;
+
+ for (int i = 0; i < newValue->nodes.size; i++)
+ {
+ if (newValue->nodes.data[i].max_connections > max_connections)
+ return false;
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/contrib/test_guc/test_guc.control b/contrib/test_guc/test_guc.control
new file mode 100644
index 0000000000..17f1864d38
--- /dev/null
+++ b/contrib/test_guc/test_guc.control
@@ -0,0 +1,5 @@
+# test_guc extension
+comment = 'Simple GUC extension with composite parameters'
+default_version = '1.0'
+module_pathname = '$libdir/test_guc'
+relocatable = true
\ No newline at end of file
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fd0fdb96d4..61c00c0826 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -163,6 +163,9 @@ int idle_replication_slot_timeout_secs = 0;
*/
char *synchronized_standby_slots;
+struct ReplicaConfig replicaConfig;
+struct ReplicaConfig replicaConfigBoot;
+
/* This is the parsed and cached configuration for synchronized_standby_slots */
static SyncStandbySlotsConfigData *synchronized_standby_slots_config;
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 32cf3a48b8..c45426afbb 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -89,6 +89,9 @@
/* User-settable parameters for sync rep */
char *SyncRepStandbyNames;
+struct SSNType ssn;
+struct SSNType ssnBoot;
+
#define SyncStandbysDefined() \
(SyncRepStandbyNames != NULL && SyncRepStandbyNames[0] != '\0')
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index b7b9692f8c..d7c8c08617 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -112,6 +112,8 @@ int Log_destination = LOG_DESTINATION_STDERR;
char *Log_destination_string = NULL;
bool syslog_sequence_numbers = true;
bool syslog_split_messages = true;
+char *logLevelNames[5];
+char *logLevelNamesBoot[5] = {"TRACE","LOG","WARNING","ERROR","FATAL"};
/* Processed form of backtrace_functions GUC */
static char *backtrace_function_list;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 545d1e90fb..e1c515b903 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -1833,6 +1833,9 @@ char *session_preload_libraries_string = NULL;
char *shared_preload_libraries_string = NULL;
char *local_preload_libraries_string = NULL;
+struct ShrLibArr sharedLibs;
+struct ShrLibArr sharedLibsBoot;
+
/* Flag telling that we are loading shared_preload_libraries */
bool process_shared_preload_libraries_in_progress = false;
bool process_shared_preload_libraries_done = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eac986a1c7..de1b278d97 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -81,7 +81,6 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
-
/*
* Unit conversion tables.
*
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c392d00972..c8409a5e9c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3481,4 +3481,32 @@
assign_hook => 'assign_io_method',
},
+{ name => 'replica', type => 'composite', context => 'PGC_USERSET', group => 'REPLICATION_PRIMARY',
+ short_desc => 'config of replica',
+ type_name => 'ReplicaConfigType',
+ variable => 'replicaConfig',
+ boot_val => '&replicaConfigBoot',
+},
+
+{ name => 'shared_preload_libraries_list',type => 'composite', context => 'PGC_USERSET', group => 'CLIENT_CONN_PRELOAD',
+ short_desc => 'list of shared preloaded libraries',
+ type_name => 'string[]',
+ variable => 'sharedLibs',
+ boot_val => '&sharedLibsBoot',
+},
+
+{ name => 'ssn', type => 'composite', context => 'PGC_USERSET', group => 'REPLICATION_PRIMARY',
+ short_desc => 'new synchronous_standby_names',
+ type_name => 'SSNType',
+ variable => 'ssn',
+ boot_val => '&ssnBoot',
+},
+
+{ name => 'log_level_names', type => 'composite', context => 'PGC_USERSET', group => 'LOGGING_WHAT',
+ short_desc => 'array of names for 5 log levels',
+ type_name => 'string[5]',
+ variable => 'logLevelNames',
+ boot_val => '&logLevelNamesBoot',
+},
+
]
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c45be54f3e..2a60b9ae13 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -817,6 +817,14 @@ struct type_definition UserDefinedConfigureTypes[] = {
sizeof(char *),
NULL
},
+ {
+ "ReplicaConfigType",
+ "bool enable_connections; int max_delay; int max_slot_size"
+ },
+ {
+ "SSNType",
+ "string mode; int threshold; string[] names"
+ },
/* End-of-list marker */
{
NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c36fcb9ab6..cd508abb5d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -882,4 +882,9 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
+#replica = {}
+#shared_preload_libraries_list = []
+#ssn = {}
+#log_level_names = []
+
# Add settings for extensions here
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 1bef98471c..01bab98f39 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -516,6 +516,15 @@ extern PGDLLIMPORT char *session_preload_libraries_string;
extern PGDLLIMPORT char *shared_preload_libraries_string;
extern PGDLLIMPORT char *local_preload_libraries_string;
+struct ShrLibArr
+{
+ char **libs;
+ int size;
+};
+
+extern PGDLLIMPORT struct ShrLibArr sharedLibs;
+extern PGDLLIMPORT struct ShrLibArr sharedLibsBoot;
+
extern void CreateDataDirLockFile(bool amPostmaster);
extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster,
const char *socketDir);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde..7735fbd058 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -294,6 +294,16 @@ extern PGDLLIMPORT int max_replication_slots;
extern PGDLLIMPORT char *synchronized_standby_slots;
extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
+struct ReplicaConfig
+{
+ bool enable_connections;
+ int max_delay;
+ int max_slots_size;
+};
+
+extern PGDLLIMPORT struct ReplicaConfig replicaConfig;
+extern PGDLLIMPORT struct ReplicaConfig replicaConfigBoot;
+
/* shmem initialization functions */
extern Size ReplicationSlotsShmemSize(void);
extern void ReplicationSlotsShmemInit(void);
diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h
index dc2b118b16..6d323264bd 100644
--- a/src/include/replication/syncrep.h
+++ b/src/include/replication/syncrep.h
@@ -76,6 +76,21 @@ extern PGDLLIMPORT SyncRepConfigData *SyncRepConfig;
/* user-settable parameters for synchronous replication */
extern PGDLLIMPORT char *SyncRepStandbyNames;
+struct DynArrStr
+{
+ char **data;
+ int size;
+};
+struct SSNType
+{
+ char *mode;
+ int threshold;
+ struct DynArrStr names;
+};
+
+extern PGDLLIMPORT struct SSNType ssn;
+extern PGDLLIMPORT struct SSNType ssnBoot;
+
/* called by user backend */
extern void SyncRepWaitForLSN(XLogRecPtr lsn, bool commit);
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 675f4f5f46..2ee3eca956 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -493,6 +493,8 @@ extern PGDLLIMPORT int Log_destination;
extern PGDLLIMPORT char *Log_destination_string;
extern PGDLLIMPORT bool syslog_sequence_numbers;
extern PGDLLIMPORT bool syslog_split_messages;
+extern PGDLLIMPORT char *logLevelNames[5];
+extern PGDLLIMPORT char *logLevelNamesBoot[5];
/* Log destination bitmap */
#define LOG_DESTINATION_STDERR 1
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index b197a81f28..2b8928439d 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -199,6 +199,7 @@ typedef const char *(*GucShowHook) (void);
/*
* Miscellaneous
*/
+
typedef enum
{
/* Types of set_config_option actions */
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6b1e730bf4..31e2341f19 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -17,6 +17,7 @@ tests += {
't/006_signal_autovacuum.pl',
't/007_catcache_inval.pl',
't/008_replslot_single_user.pl',
+ 't/009_test_guc_composite.pl',
],
},
}
diff --git a/src/test/modules/test_misc/t/009_test_guc_composite.pl b/src/test/modules/test_misc/t/009_test_guc_composite.pl
new file mode 100644
index 0000000000..58b1cbc91c
--- /dev/null
+++ b/src/test/modules/test_misc/t/009_test_guc_composite.pl
@@ -0,0 +1,49 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+# Tests composite GUC parameters
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Test ALTER SYSTEM command:
+# ALTER SYSTEM must only write changed parameter in .auto.conf file
+$node->safe_psql(
+ 'postgres',
+ "ALTER SYSTEM SET replica->max_delay = 42"
+);
+
+# Check postgresql.auto.conf
+my $auto_conf_path = $node->data_dir . '/postgresql.auto.conf';
+my $expected_line = "replica = {enable_connections: \'false\', max_delay: \'42\', max_slot_size: \'0\'}";
+my $found = 0;
+open(my $fh, '<', $auto_conf_path) or die "Cannot open file: $!";
+while (my $line = <$fh>) {
+ chomp $line;
+ if ($line =~ /^\s*\Q$expected_line\E\s*$/) {
+ $found = 1;
+ last;
+ }
+}
+close($fh);
+
+ok($found, 'Composite parameter setting found in postgresql.auto.conf');
+
+#reload config
+$node->safe_psql('postgres', 'SELECT pg_reload_conf()');
+
+# Check that parameter was applied
+my $current_value = $node->safe_psql(
+ 'postgres',
+ "SHOW replica->max_delay"
+);
+
+is($current_value, '42', 'Composite parameter value is correctly set');
+
+done_testing();
diff --git a/src/test/regress/expected/guc_composite.out b/src/test/regress/expected/guc_composite.out
new file mode 100644
index 0000000000..0eafceed60
--- /dev/null
+++ b/src/test/regress/expected/guc_composite.out
@@ -0,0 +1,203 @@
+-- STRUCT
+SHOW replica;
+ replica
+------------------------------------
+ { +
+ enable_connections: false,+
+ max_delay: 0, +
+ max_slot_size: 0 +
+ }
+(1 row)
+
+SET replica->max_delay to 42;
+SHOW replica->max_delay;
+ replica
+---------
+ 42
+(1 row)
+
+SET replica to {enable_connections: true, max_slot_size: 10};
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 42, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SET replica->invalid_field to 4;
+ERROR: invalid value for parameter "replica": "{invalid_field: '4'}"
+HINT: incorrect field name at or near ":"
+-- STATIC ARRAY
+SHOW log_level_names;
+ log_level_names
+--------------------
+ [ +
+ 'TRACE', +
+ 'LOG', +
+ 'WARNING',+
+ 'ERROR', +
+ 'FATAL' +
+ ]
+(1 row)
+
+SET log_level_names[0] to 'DEBUG';
+SHOW log_level_names[0];
+ log_level_names
+-----------------
+ 'DEBUG'
+(1 row)
+
+SET log_level_names[10] to 'EXTRA';
+ERROR: invalid value for parameter "log_level_names": "[10: 'EXTRA']"
+HINT: there is index which is greater than size of array at or near ":"
+SET log_level_names[-1] to 'EXTRA';
+ERROR: syntax error at or near "-"
+LINE 1: SET log_level_names[-1] to 'EXTRA';
+ ^
+-- DYNAMIC ARRAY
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+-------------------------------
+ [ +
+ ]
+(1 row)
+
+SET shared_preload_libraries_list[0] to 'yet_another_ext';
+SHOW shared_preload_libraries_list->data[0];
+ shared_preload_libraries_list
+-------------------------------
+ 'yet_another_ext'
+(1 row)
+
+SET extended_guc_arrays to true;
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+-----------------------------------
+ { +
+ size: 1, +
+ data: [ +
+ 'yet_another_ext'+
+ ] +
+ }
+(1 row)
+
+SET shared_preload_libraries_list->size to 2;
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+------------------------------------
+ { +
+ size: 2, +
+ data: [ +
+ 'yet_another_ext',+
+ nil +
+ ] +
+ }
+(1 row)
+
+SET shared_preload_libraries_list to {size: 2, data: [3: 'third_ext']};
+ERROR: invalid value for parameter "shared_preload_libraries_list": "{size: 2, data: [3: 'third_ext']}"
+HINT: there is index greater than array length. Change "size" field at or near "'third_ext'"
+SET extended_guc_arrays to false;
+-- CHECK GUC STACK
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 42, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET replica->max_delay to 6;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+COMMIT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET replica->max_delay to 28;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 28, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+ABORT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET LOCAL replica->max_delay to 28;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 28, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+COMMIT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae6..d6d41bbccc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -102,7 +102,7 @@ test: publication subscription
# Another group of parallel tests
# select_views depends on create_view
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc_composite guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
# ----------
# Another group of parallel tests (JSON related)
diff --git a/src/test/regress/sql/guc_composite.sql b/src/test/regress/sql/guc_composite.sql
new file mode 100644
index 0000000000..338cc8692c
--- /dev/null
+++ b/src/test/regress/sql/guc_composite.sql
@@ -0,0 +1,55 @@
+-- STRUCT
+SHOW replica;
+
+SET replica->max_delay to 42;
+SHOW replica->max_delay;
+
+SET replica to {enable_connections: true, max_slot_size: 10};
+SHOW replica;
+SET replica->invalid_field to 4;
+
+-- STATIC ARRAY
+SHOW log_level_names;
+SET log_level_names[0] to 'DEBUG';
+SHOW log_level_names[0];
+SET log_level_names[10] to 'EXTRA';
+SET log_level_names[-1] to 'EXTRA';
+
+-- DYNAMIC ARRAY
+
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list[0] to 'yet_another_ext';
+SHOW shared_preload_libraries_list->data[0];
+
+SET extended_guc_arrays to true;
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list->size to 2;
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list to {size: 2, data: [3: 'third_ext']};
+SET extended_guc_arrays to false;
+
+-- CHECK GUC STACK
+
+SHOW replica;
+BEGIN;
+SET replica->max_delay to 6;
+SHOW replica;
+COMMIT;
+SHOW replica;
+
+SHOW replica;
+BEGIN;
+SET replica->max_delay to 28;
+SHOW replica;
+ABORT;
+SHOW replica;
+
+SHOW replica;
+BEGIN;
+SET LOCAL replica->max_delay to 28;
+SHOW replica;
+COMMIT;
+SHOW replica;
--
2.48.1
Hello hackers,
The new version of the patch adds support for multi-line writing of composite type values in the postgresql.conf file. Hidden fields have also been added. Such fields may be required to protect the private part of the state of a composite option from an external user. In order for the field to be hidden, the composite type signature must describe only the field type without the field name.
Please note that all allocated resources used within hidden fields should use only guc_malloc. This is necessary to automatically release resources.
The patch applies cleanly to the master (9fc7f6ab7226d7c9dbe4ff333130c82f92749f69)
Best regards,
Anton Chumak
Attachments:
v2-0002-added-guc-options-for-test-purposes.patchtext/x-patchDownload
From 9dddc5e43e033babe22209a835c208ef574a2b8c Mon Sep 17 00:00:00 2001
From: Anton Chumak <A.M.Chumak@yandex.com>
Date: Mon, 15 Sep 2025 17:28:50 +0700
Subject: [PATCH 2/2] added guc options for test purposes
Added test composite GUC options replica, shared_preload_libraries,
log_level_names. Each option represents one of the main composite types:
structure, dynamic array, and static array, respectively. Also, for example,
the ssn option and the test_guc extension with their own test options
were added. Regression and TAP tests were performed for the test options.
---
contrib/test_guc/Makefile | 18 ++
contrib/test_guc/test_guc--1.0.sql | 0
contrib/test_guc/test_guc.c | 118 ++++++++++
contrib/test_guc/test_guc.control | 5 +
src/backend/replication/slot.c | 3 +
src/backend/replication/syncrep.c | 3 +
src/backend/utils/error/elog.c | 2 +
src/backend/utils/init/miscinit.c | 3 +
src/backend/utils/misc/guc.c | 1 -
src/backend/utils/misc/guc_parameters.dat | 28 +++
src/backend/utils/misc/guc_tables.c | 8 +
src/backend/utils/misc/postgresql.conf.sample | 5 +
src/include/miscadmin.h | 9 +
src/include/replication/slot.h | 10 +
src/include/replication/syncrep.h | 15 ++
src/include/utils/elog.h | 2 +
src/include/utils/guc.h | 1 +
src/test/modules/test_misc/meson.build | 1 +
.../test_misc/t/009_test_guc_composite.pl | 49 +++++
src/test/regress/expected/guc_composite.out | 203 ++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/guc_composite.sql | 55 +++++
22 files changed, 539 insertions(+), 2 deletions(-)
create mode 100644 contrib/test_guc/Makefile
create mode 100644 contrib/test_guc/test_guc--1.0.sql
create mode 100644 contrib/test_guc/test_guc.c
create mode 100644 contrib/test_guc/test_guc.control
create mode 100644 src/test/modules/test_misc/t/009_test_guc_composite.pl
create mode 100644 src/test/regress/expected/guc_composite.out
create mode 100644 src/test/regress/sql/guc_composite.sql
diff --git a/contrib/test_guc/Makefile b/contrib/test_guc/Makefile
new file mode 100644
index 0000000000..6e844776c6
--- /dev/null
+++ b/contrib/test_guc/Makefile
@@ -0,0 +1,18 @@
+# contrib/test_guc/Makefile
+MODULE_big = test_guc
+OBJS = \
+ $(WIN32RES) \
+ test_guc.o
+EXTENSION = test_guc
+DATA = test_guc--1.0.sql
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/test_guc
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
\ No newline at end of file
diff --git a/contrib/test_guc/test_guc--1.0.sql b/contrib/test_guc/test_guc--1.0.sql
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/contrib/test_guc/test_guc.c b/contrib/test_guc/test_guc.c
new file mode 100644
index 0000000000..15148ab11d
--- /dev/null
+++ b/contrib/test_guc/test_guc.c
@@ -0,0 +1,118 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/guc.h"
+#include "utils/guc_tables.h"
+#include "utils/elog.h"
+#include "string.h"
+
+PG_MODULE_MAGIC;
+
+struct LogLevel
+{
+ char *name;
+ int level;
+};
+
+struct LogLevel logLevelsMapping[5];
+struct LogLevel logLevelsMappingBoot[5];
+
+
+struct NodeConfig
+{
+ char *name;
+ char *ip;
+ int port;
+ int max_connections;
+ bool enable_connections;
+};
+
+struct NodeArray
+{
+ struct NodeConfig *data;
+ int size;
+};
+
+struct ClusterConfig
+{
+ char *name;
+ char *leader;
+ struct NodeArray nodes;
+};
+
+struct ClusterConfig clusterConfig;
+struct ClusterConfig clusterConfigBoot;
+
+
+bool validateCluster(void *newValue, void **extra, GucSource source);
+
+void _PG_init(void);
+
+void
+_PG_init(void)
+{
+ DefineCustomCompositeType("ext.loglevelmap", "string name; int level");
+
+ DefineCustomCompositeVariable("ext.loglevels",
+ "Mapping between level names and values",
+ NULL,
+ "ext.loglevelmap[5]",
+ &logLevelsMapping,
+ &logLevelsMappingBoot,
+ PGC_USERSET,
+ 0,
+ NULL,
+ NULL,
+ NULL);
+
+
+
+ DefineCustomCompositeType("ext.nodeConfig", "string name; string ip; int port; int max_connections; bool enable_connections");
+ DefineCustomCompositeType("ext.clusterConfig", "string name; string leader; ext.nodeConfig[] nodes");
+
+ DefineCustomCompositeVariable("ext.cluster",
+ "Example of multi-level structure",
+ NULL,
+ "ext.clusterConfig",
+ &clusterConfig,
+ &clusterConfigBoot,
+ PGC_USERSET,
+ 0,
+ &validateCluster,
+ NULL,
+ NULL);
+
+ MarkGUCPrefixReserved("ext");
+}
+
+
+bool
+validateCluster(void *newValueRaw, void **extra, GucSource source)
+{
+ struct ClusterConfig *newValue = (struct ClusterConfig *)newValueRaw;
+ bool found_leader = false;
+ int max_connections = 0;
+
+ if (newValue->name == NULL)
+ return true;
+
+ for (int i = 0; i < newValue->nodes.size; i++)
+ {
+ if (strcmp(newValue->leader, newValue->nodes.data[i].name) == 0)
+ {
+ found_leader = true;
+ max_connections = newValue->nodes.data[i].max_connections;
+ break;
+ }
+ }
+
+ if (!found_leader)
+ return false;
+
+ for (int i = 0; i < newValue->nodes.size; i++)
+ {
+ if (newValue->nodes.data[i].max_connections > max_connections)
+ return false;
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/contrib/test_guc/test_guc.control b/contrib/test_guc/test_guc.control
new file mode 100644
index 0000000000..17f1864d38
--- /dev/null
+++ b/contrib/test_guc/test_guc.control
@@ -0,0 +1,5 @@
+# test_guc extension
+comment = 'Simple GUC extension with composite parameters'
+default_version = '1.0'
+module_pathname = '$libdir/test_guc'
+relocatable = true
\ No newline at end of file
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index fd0fdb96d4..61c00c0826 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -163,6 +163,9 @@ int idle_replication_slot_timeout_secs = 0;
*/
char *synchronized_standby_slots;
+struct ReplicaConfig replicaConfig;
+struct ReplicaConfig replicaConfigBoot;
+
/* This is the parsed and cached configuration for synchronized_standby_slots */
static SyncStandbySlotsConfigData *synchronized_standby_slots_config;
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 32cf3a48b8..c45426afbb 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -89,6 +89,9 @@
/* User-settable parameters for sync rep */
char *SyncRepStandbyNames;
+struct SSNType ssn;
+struct SSNType ssnBoot;
+
#define SyncStandbysDefined() \
(SyncRepStandbyNames != NULL && SyncRepStandbyNames[0] != '\0')
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index b7b9692f8c..d7c8c08617 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -112,6 +112,8 @@ int Log_destination = LOG_DESTINATION_STDERR;
char *Log_destination_string = NULL;
bool syslog_sequence_numbers = true;
bool syslog_split_messages = true;
+char *logLevelNames[5];
+char *logLevelNamesBoot[5] = {"TRACE","LOG","WARNING","ERROR","FATAL"};
/* Processed form of backtrace_functions GUC */
static char *backtrace_function_list;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 545d1e90fb..e1c515b903 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -1833,6 +1833,9 @@ char *session_preload_libraries_string = NULL;
char *shared_preload_libraries_string = NULL;
char *local_preload_libraries_string = NULL;
+struct ShrLibArr sharedLibs;
+struct ShrLibArr sharedLibsBoot;
+
/* Flag telling that we are loading shared_preload_libraries */
bool process_shared_preload_libraries_in_progress = false;
bool process_shared_preload_libraries_done = false;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eac986a1c7..de1b278d97 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -81,7 +81,6 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
-
/*
* Unit conversion tables.
*
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index c392d00972..c8409a5e9c 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -3481,4 +3481,32 @@
assign_hook => 'assign_io_method',
},
+{ name => 'replica', type => 'composite', context => 'PGC_USERSET', group => 'REPLICATION_PRIMARY',
+ short_desc => 'config of replica',
+ type_name => 'ReplicaConfigType',
+ variable => 'replicaConfig',
+ boot_val => '&replicaConfigBoot',
+},
+
+{ name => 'shared_preload_libraries_list',type => 'composite', context => 'PGC_USERSET', group => 'CLIENT_CONN_PRELOAD',
+ short_desc => 'list of shared preloaded libraries',
+ type_name => 'string[]',
+ variable => 'sharedLibs',
+ boot_val => '&sharedLibsBoot',
+},
+
+{ name => 'ssn', type => 'composite', context => 'PGC_USERSET', group => 'REPLICATION_PRIMARY',
+ short_desc => 'new synchronous_standby_names',
+ type_name => 'SSNType',
+ variable => 'ssn',
+ boot_val => '&ssnBoot',
+},
+
+{ name => 'log_level_names', type => 'composite', context => 'PGC_USERSET', group => 'LOGGING_WHAT',
+ short_desc => 'array of names for 5 log levels',
+ type_name => 'string[5]',
+ variable => 'logLevelNames',
+ boot_val => '&logLevelNamesBoot',
+},
+
]
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c45be54f3e..2a60b9ae13 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -817,6 +817,14 @@ struct type_definition UserDefinedConfigureTypes[] = {
sizeof(char *),
NULL
},
+ {
+ "ReplicaConfigType",
+ "bool enable_connections; int max_delay; int max_slot_size"
+ },
+ {
+ "SSNType",
+ "string mode; int threshold; string[] names"
+ },
/* End-of-list marker */
{
NULL, NULL
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index c36fcb9ab6..cd508abb5d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -882,4 +882,9 @@ autovacuum_worker_slots = 16 # autovacuum worker slots to allocate
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
+#replica = {}
+#shared_preload_libraries_list = []
+#ssn = {}
+#log_level_names = []
+
# Add settings for extensions here
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 1bef98471c..01bab98f39 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -516,6 +516,15 @@ extern PGDLLIMPORT char *session_preload_libraries_string;
extern PGDLLIMPORT char *shared_preload_libraries_string;
extern PGDLLIMPORT char *local_preload_libraries_string;
+struct ShrLibArr
+{
+ char **libs;
+ int size;
+};
+
+extern PGDLLIMPORT struct ShrLibArr sharedLibs;
+extern PGDLLIMPORT struct ShrLibArr sharedLibsBoot;
+
extern void CreateDataDirLockFile(bool amPostmaster);
extern void CreateSocketLockFile(const char *socketfile, bool amPostmaster,
const char *socketDir);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index fe62162cde..7735fbd058 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -294,6 +294,16 @@ extern PGDLLIMPORT int max_replication_slots;
extern PGDLLIMPORT char *synchronized_standby_slots;
extern PGDLLIMPORT int idle_replication_slot_timeout_secs;
+struct ReplicaConfig
+{
+ bool enable_connections;
+ int max_delay;
+ int max_slots_size;
+};
+
+extern PGDLLIMPORT struct ReplicaConfig replicaConfig;
+extern PGDLLIMPORT struct ReplicaConfig replicaConfigBoot;
+
/* shmem initialization functions */
extern Size ReplicationSlotsShmemSize(void);
extern void ReplicationSlotsShmemInit(void);
diff --git a/src/include/replication/syncrep.h b/src/include/replication/syncrep.h
index dc2b118b16..6d323264bd 100644
--- a/src/include/replication/syncrep.h
+++ b/src/include/replication/syncrep.h
@@ -76,6 +76,21 @@ extern PGDLLIMPORT SyncRepConfigData *SyncRepConfig;
/* user-settable parameters for synchronous replication */
extern PGDLLIMPORT char *SyncRepStandbyNames;
+struct DynArrStr
+{
+ char **data;
+ int size;
+};
+struct SSNType
+{
+ char *mode;
+ int threshold;
+ struct DynArrStr names;
+};
+
+extern PGDLLIMPORT struct SSNType ssn;
+extern PGDLLIMPORT struct SSNType ssnBoot;
+
/* called by user backend */
extern void SyncRepWaitForLSN(XLogRecPtr lsn, bool commit);
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 675f4f5f46..2ee3eca956 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -493,6 +493,8 @@ extern PGDLLIMPORT int Log_destination;
extern PGDLLIMPORT char *Log_destination_string;
extern PGDLLIMPORT bool syslog_sequence_numbers;
extern PGDLLIMPORT bool syslog_split_messages;
+extern PGDLLIMPORT char *logLevelNames[5];
+extern PGDLLIMPORT char *logLevelNamesBoot[5];
/* Log destination bitmap */
#define LOG_DESTINATION_STDERR 1
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index b197a81f28..2b8928439d 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -199,6 +199,7 @@ typedef const char *(*GucShowHook) (void);
/*
* Miscellaneous
*/
+
typedef enum
{
/* Types of set_config_option actions */
diff --git a/src/test/modules/test_misc/meson.build b/src/test/modules/test_misc/meson.build
index 6b1e730bf4..31e2341f19 100644
--- a/src/test/modules/test_misc/meson.build
+++ b/src/test/modules/test_misc/meson.build
@@ -17,6 +17,7 @@ tests += {
't/006_signal_autovacuum.pl',
't/007_catcache_inval.pl',
't/008_replslot_single_user.pl',
+ 't/009_test_guc_composite.pl',
],
},
}
diff --git a/src/test/modules/test_misc/t/009_test_guc_composite.pl b/src/test/modules/test_misc/t/009_test_guc_composite.pl
new file mode 100644
index 0000000000..58b1cbc91c
--- /dev/null
+++ b/src/test/modules/test_misc/t/009_test_guc_composite.pl
@@ -0,0 +1,49 @@
+# Copyright (c) 2024-2025, PostgreSQL Global Development Group
+
+# Tests composite GUC parameters
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Test ALTER SYSTEM command:
+# ALTER SYSTEM must only write changed parameter in .auto.conf file
+$node->safe_psql(
+ 'postgres',
+ "ALTER SYSTEM SET replica->max_delay = 42"
+);
+
+# Check postgresql.auto.conf
+my $auto_conf_path = $node->data_dir . '/postgresql.auto.conf';
+my $expected_line = "replica = {enable_connections: \'false\', max_delay: \'42\', max_slot_size: \'0\'}";
+my $found = 0;
+open(my $fh, '<', $auto_conf_path) or die "Cannot open file: $!";
+while (my $line = <$fh>) {
+ chomp $line;
+ if ($line =~ /^\s*\Q$expected_line\E\s*$/) {
+ $found = 1;
+ last;
+ }
+}
+close($fh);
+
+ok($found, 'Composite parameter setting found in postgresql.auto.conf');
+
+#reload config
+$node->safe_psql('postgres', 'SELECT pg_reload_conf()');
+
+# Check that parameter was applied
+my $current_value = $node->safe_psql(
+ 'postgres',
+ "SHOW replica->max_delay"
+);
+
+is($current_value, '42', 'Composite parameter value is correctly set');
+
+done_testing();
diff --git a/src/test/regress/expected/guc_composite.out b/src/test/regress/expected/guc_composite.out
new file mode 100644
index 0000000000..0eafceed60
--- /dev/null
+++ b/src/test/regress/expected/guc_composite.out
@@ -0,0 +1,203 @@
+-- STRUCT
+SHOW replica;
+ replica
+------------------------------------
+ { +
+ enable_connections: false,+
+ max_delay: 0, +
+ max_slot_size: 0 +
+ }
+(1 row)
+
+SET replica->max_delay to 42;
+SHOW replica->max_delay;
+ replica
+---------
+ 42
+(1 row)
+
+SET replica to {enable_connections: true, max_slot_size: 10};
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 42, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SET replica->invalid_field to 4;
+ERROR: invalid value for parameter "replica": "{invalid_field: '4'}"
+HINT: incorrect field name at or near ":"
+-- STATIC ARRAY
+SHOW log_level_names;
+ log_level_names
+--------------------
+ [ +
+ 'TRACE', +
+ 'LOG', +
+ 'WARNING',+
+ 'ERROR', +
+ 'FATAL' +
+ ]
+(1 row)
+
+SET log_level_names[0] to 'DEBUG';
+SHOW log_level_names[0];
+ log_level_names
+-----------------
+ 'DEBUG'
+(1 row)
+
+SET log_level_names[10] to 'EXTRA';
+ERROR: invalid value for parameter "log_level_names": "[10: 'EXTRA']"
+HINT: there is index which is greater than size of array at or near ":"
+SET log_level_names[-1] to 'EXTRA';
+ERROR: syntax error at or near "-"
+LINE 1: SET log_level_names[-1] to 'EXTRA';
+ ^
+-- DYNAMIC ARRAY
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+-------------------------------
+ [ +
+ ]
+(1 row)
+
+SET shared_preload_libraries_list[0] to 'yet_another_ext';
+SHOW shared_preload_libraries_list->data[0];
+ shared_preload_libraries_list
+-------------------------------
+ 'yet_another_ext'
+(1 row)
+
+SET extended_guc_arrays to true;
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+-----------------------------------
+ { +
+ size: 1, +
+ data: [ +
+ 'yet_another_ext'+
+ ] +
+ }
+(1 row)
+
+SET shared_preload_libraries_list->size to 2;
+SHOW shared_preload_libraries_list;
+ shared_preload_libraries_list
+------------------------------------
+ { +
+ size: 2, +
+ data: [ +
+ 'yet_another_ext',+
+ nil +
+ ] +
+ }
+(1 row)
+
+SET shared_preload_libraries_list to {size: 2, data: [3: 'third_ext']};
+ERROR: invalid value for parameter "shared_preload_libraries_list": "{size: 2, data: [3: 'third_ext']}"
+HINT: there is index greater than array length. Change "size" field at or near "'third_ext'"
+SET extended_guc_arrays to false;
+-- CHECK GUC STACK
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 42, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET replica->max_delay to 6;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+COMMIT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET replica->max_delay to 28;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 28, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+ABORT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+BEGIN;
+SET LOCAL replica->max_delay to 28;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 28, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
+COMMIT;
+SHOW replica;
+ replica
+-----------------------------------
+ { +
+ enable_connections: true,+
+ max_delay: 6, +
+ max_slot_size: 10 +
+ }
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fbffc67ae6..d6d41bbccc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -102,7 +102,7 @@ test: publication subscription
# Another group of parallel tests
# select_views depends on create_view
# ----------
-test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
+test: select_views portals_p2 foreign_key cluster dependency guc_composite guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock indirect_toast equivclass
# ----------
# Another group of parallel tests (JSON related)
diff --git a/src/test/regress/sql/guc_composite.sql b/src/test/regress/sql/guc_composite.sql
new file mode 100644
index 0000000000..338cc8692c
--- /dev/null
+++ b/src/test/regress/sql/guc_composite.sql
@@ -0,0 +1,55 @@
+-- STRUCT
+SHOW replica;
+
+SET replica->max_delay to 42;
+SHOW replica->max_delay;
+
+SET replica to {enable_connections: true, max_slot_size: 10};
+SHOW replica;
+SET replica->invalid_field to 4;
+
+-- STATIC ARRAY
+SHOW log_level_names;
+SET log_level_names[0] to 'DEBUG';
+SHOW log_level_names[0];
+SET log_level_names[10] to 'EXTRA';
+SET log_level_names[-1] to 'EXTRA';
+
+-- DYNAMIC ARRAY
+
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list[0] to 'yet_another_ext';
+SHOW shared_preload_libraries_list->data[0];
+
+SET extended_guc_arrays to true;
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list->size to 2;
+SHOW shared_preload_libraries_list;
+
+SET shared_preload_libraries_list to {size: 2, data: [3: 'third_ext']};
+SET extended_guc_arrays to false;
+
+-- CHECK GUC STACK
+
+SHOW replica;
+BEGIN;
+SET replica->max_delay to 6;
+SHOW replica;
+COMMIT;
+SHOW replica;
+
+SHOW replica;
+BEGIN;
+SET replica->max_delay to 28;
+SHOW replica;
+ABORT;
+SHOW replica;
+
+SHOW replica;
+BEGIN;
+SET LOCAL replica->max_delay to 28;
+SHOW replica;
+COMMIT;
+SHOW replica;
--
2.48.1
v2-0001-Composite-data-types-have-been-added-to-the-GUC.patchtext/x-patchDownload
From 734279aad5851d4513892f44de9479245ff0f47d Mon Sep 17 00:00:00 2001
From: Anton Chumak <A.M.Chumak@yandex.com>
Date: Mon, 8 Sep 2025 10:10:10 +0700
Subject: [PATCH 1/2] Composite data types have been added to the GUC
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The new module of the configuration system allows you to map the values of
composite data types into composite variables (structures, static arrays,
dynamic arrays) in the C code in a unified way.
Mapping works due to the signature that the programmer declares for the
composite type. For core options, the declaration data is in the
UserDefinedConfigureTypes array. Composite types for options from
extensions are declared using the DefineCustomCompositeType function.
All declarations must be arranged topologically. That is, if the type A
option contains a type B field, then type B must first be declared,
and then type A.
The main fields in the type definition are the type name and its signature.
The type signature has the following syntax:
“field_type field_name; field_type field_name; ...; field_type field_name”
where field_type is the already registered type, field_name is the field name.
There are also data types that do not need to be declared - these are arrays.
So, if there is a registered type A, then data types automatically become
available: A[n] is a static array of length n and A[] is a dynamic array.
Note that the declared type signature must match the signature of the structure
in the C code, since it will then be used to calculate the alignment of fields
according to the rules of the C language.
Dynamic arrays are always mapped into a structure like:
struct DynArr {
void *data; //pointer to data
int size; //length of the array
}
After declaring the type, you can declare a composite type configuration
option. The core options are declared in the guc_parameters.dat file. They must
specify the type => ‘composite’ fields and specify in the type_name field
the name of the composite type that was declared earlier. In the boot_val
field, write a pointer to a global variable that will store this value.
Options from extensions are declared using DefineCustomCompositeVariable.
Now you can use the following syntax to work with the new options both in
the configuration file and in psql:
Field access: option_name->field_name
Access to an array element: option_name[index]
You can combine these access methods.
Dynamic arrays always have implicit fields data and size.
data is the data of the array, size is its length.
Values of composite types have the following syntax:
- Structures: {field: value, ..., field: value}
- Static arrays: [index: value, index: value]
- Dynamic arrays arrays have implicit fields, so you can use 2 syntaxes:
-- compact (same as for static arrays) and
-- extended:
{data: [index: value, .., index: value], size: value}.
It is not necessary to write indexes in array values. If you write
without indexes, it is assumed that indexing starts from 0 with an increment
of 1. In this case, all elements within the same array must be either with
or without indexes.
When using the show command, the display of the dynamic array depends on the
extended_guc_arrays option. If this flag is true, then the extended form
is used, otherwise the compact form is used.
String values within composite types also support escape sequences.
All the functionality available to scalar options is also
The system uses incremental semantics. This means that when writing to a .conf
file or the set command, only the specified fields of the structure will be
changed, the remaining fields will not be involved. The same applies to the
alter system see. When using this command, the current value will be written
to the .auto.conf file with the changed fields that were described when
calling the command, while the current value of the option will not change.
---
src/backend/Makefile | 2 +-
src/backend/nodes/makefuncs.c | 16 +
src/backend/nodes/value.c | 14 +
src/backend/parser/gram.y | 72 +-
src/backend/parser/scan.l | 11 +-
src/backend/utils/misc/Makefile | 16 +
src/backend/utils/misc/README | 118 ++
src/backend/utils/misc/gen_guc_tables.pl | 4 +-
src/backend/utils/misc/guc-file.l | 29 +-
src/backend/utils/misc/guc.c | 1120 +++++++++++++-
src/backend/utils/misc/guc_composite.c | 1529 +++++++++++++++++++
src/backend/utils/misc/guc_composite.h | 84 +
src/backend/utils/misc/guc_composite_gram.y | 787 ++++++++++
src/backend/utils/misc/guc_composite_scan.l | 132 ++
src/backend/utils/misc/guc_funcs.c | 113 +-
src/backend/utils/misc/guc_parameters.dat | 6 +
src/backend/utils/misc/guc_tables.c | 64 +-
src/backend/utils/misc/help_config.c | 13 +
src/backend/utils/misc/meson.build | 26 +
src/include/nodes/makefuncs.h | 1 +
src/include/nodes/value.h | 10 +
src/include/utils/guc.h | 26 +
src/include/utils/guc_tables.h | 40 +
src/interfaces/ecpg/preproc/parse.pl | 4 +
24 files changed, 4159 insertions(+), 78 deletions(-)
create mode 100644 src/backend/utils/misc/guc_composite.c
create mode 100644 src/backend/utils/misc/guc_composite.h
create mode 100644 src/backend/utils/misc/guc_composite_gram.y
create mode 100644 src/backend/utils/misc/guc_composite_scan.l
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 7344c8c7f5..d270596ed4 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -166,7 +166,7 @@ generated-parser-sources:
$(MAKE) -C bootstrap bootparse.c bootparse.h bootscanner.c
$(MAKE) -C replication repl_gram.c repl_gram.h repl_scanner.c syncrep_gram.c syncrep_gram.h syncrep_scanner.c
$(MAKE) -C utils/adt jsonpath_gram.c jsonpath_gram.h jsonpath_scan.c
- $(MAKE) -C utils/misc guc-file.c
+ $(MAKE) -C utils/misc guc-file.c guc_composite_gram.c guc_composite_gram.h guc_composite_scan.c
##########################################################################
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e2d9e9be41..4707d20749 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -626,6 +626,22 @@ makeStringConst(char *str, int location)
return (Node *) n;
}
+/*
+ * makeSerializedCompositeConst -
+ * build a A_Const node of type T_SerializedComposite for given string
+ */
+Node *
+makeSerializedCompositeConst(char *str, int location)
+{
+ A_Const *n = makeNode(A_Const);
+
+ n->val.sval.type = T_SerializedComposite;
+ n->val.sval.sval = str;
+ n->location = location;
+
+ return (Node *) n;
+}
+
/*
* makeDefElem -
* build a DefElem node
diff --git a/src/backend/nodes/value.c b/src/backend/nodes/value.c
index 5a8c1ce247..e3af859900 100644
--- a/src/backend/nodes/value.c
+++ b/src/backend/nodes/value.c
@@ -81,3 +81,17 @@ makeBitString(char *str)
v->bsval = str;
return v;
}
+
+/*
+ * makeSerializedComposite
+ *
+ * Caller is responsible for passing a palloc'd string.
+ */
+SerializedComposite *
+makeSerializedComposite(char *str)
+{
+ SerializedComposite *v = makeNode(SerializedComposite);
+
+ v->sval = str;
+ return v;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f..48f6291323 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -575,9 +575,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> Iconst SignedIconst
%type <str> Sconst comment_text notify_payload
-%type <str> RoleId opt_boolean_or_string
+%type <str> RoleId opt_boolean_or_string opt_boolean_or_string_extended
%type <list> var_list
-%type <str> ColId ColLabel BareColLabel
+%type <str> ColId ColLabel BareColLabel IndexCh
+%type <str> composite_only list_expr list_item
%type <str> NonReservedWord NonReservedWord_or_Sconst
%type <str> var_name type_function_name param_name
%type <str> createdb_opt_name plassign_target
@@ -684,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* DOT_DOT is unused in the core SQL grammar, and so will always provoke
* parse errors. It is needed by PL/pgSQL.
*/
-%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
+%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST DEREF Op
%token <ival> ICONST PARAM
%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
@@ -889,6 +890,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%left AT /* sets precedence for AT TIME ZONE, AT LOCAL */
%left COLLATE
%right UMINUS
+%left '{' '}' /* that is '{' and '}' */
%left '[' ']'
%left '(' ')'
%left TYPECAST
@@ -1842,8 +1844,14 @@ set_rest_more: /* Generic SET syntaxes: */
var_name: ColId { $$ = $1; }
| var_name '.' ColId
{ $$ = psprintf("%s.%s", $1, $3); }
+ | var_name DEREF ColId
+ { $$ = psprintf("%s->%s", $1, $3); }
+ | var_name IndexCh
+ { $$ = psprintf("%s%s", $1, $2); }
;
+IndexCh: '['ICONST']' { $$ = psprintf("[%d]", $2); };
+
var_list: var_value { $$ = list_make1($1); }
| var_list ',' var_value { $$ = lappend($1, $3); }
;
@@ -1852,7 +1860,51 @@ var_value: opt_boolean_or_string
{ $$ = makeStringConst($1, @1); }
| NumericOnly
{ $$ = makeAConst($1, @1); }
- ;
+ | composite_only
+ { $$ = makeSerializedCompositeConst($1, @1); }
+ ;
+
+composite_only: '{' list_expr '}'
+ { $$ = psprintf("{%s}", $2); }
+ | '{' '}'
+ { $$ = psprintf("{}"); }
+ | '[' list_expr ']'
+ { $$ = psprintf("[%s]", $2); }
+ | '[' ']'
+ { $$ = psprintf("[]"); }
+ ;
+
+list_expr: list_item
+ { $$ = psprintf("%s", $1); }
+ | list_expr ',' list_item
+ { $$ = psprintf("%s, %s", $1, $3); }
+ ;
+
+list_item: ICONST ':' ICONST
+ { $$ = psprintf("%d: %d", $1, $3); }
+ | ICONST ':' FCONST
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | ICONST ':' opt_boolean_or_string_extended
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | ICONST ':' composite_only
+ { $$ = psprintf("%d: %s", $1, $3); }
+ | opt_boolean_or_string ':' ICONST
+ { $$ = psprintf("%s: %d", $1, $3); }
+ | opt_boolean_or_string ':' FCONST
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | opt_boolean_or_string ':' opt_boolean_or_string_extended
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | opt_boolean_or_string ':' composite_only
+ { $$ = psprintf("%s: %s", $1, $3); }
+ | ICONST
+ { $$ = psprintf("%d", $1); }
+ | FCONST
+ { $$ = psprintf("%s", $1); }
+ | opt_boolean_or_string_extended
+ { $$ = psprintf("%s", $1); }
+ | composite_only
+ { $$ = psprintf("%s", $1); }
+ ;
iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| READ COMMITTED { $$ = "read committed"; }
@@ -1860,6 +1912,18 @@ iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| SERIALIZABLE { $$ = "serializable"; }
;
+opt_boolean_or_string_extended:
+ TRUE_P { $$ = "true"; }
+ | FALSE_P { $$ = "false"; }
+ | ON { $$ = "on"; }
+ /*
+ * OFF is also accepted as a boolean value, but is handled by
+ * the NonReservedWord rule. The action for booleans and strings
+ * is the same, so we don't need to distinguish them here.
+ */
+ | NonReservedWord_or_Sconst { $$ = psprintf("'%s'", $1); }
+ ;
+
opt_boolean_or_string:
TRUE_P { $$ = "true"; }
| FALSE_P { $$ = "false"; }
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index 08990831fe..8a7bc6533f 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -331,7 +331,7 @@ xcinside [^*/]+
ident_start [A-Za-z\200-\377_]
ident_cont [A-Za-z\200-\377_0-9\$]
-
+dereference "->"
identifier {ident_start}{ident_cont}*
/* Assorted special-case operators and operator-like tokens */
@@ -363,7 +363,7 @@ not_equals "!="
* If you change either set, adjust the character lists appearing in the
* rule for "operator"!
*/
-self [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
+self [,()\[\].;\:\+\-\*\/\%\^\<\>\=\{\}]
op_chars [\~\!\@\#\^\&\|\`\?\+\-\*\/\%\<\>\=]
operator {op_chars}+
@@ -883,6 +883,11 @@ other .
return yytext[0];
}
+{dereference} {
+ SET_YYLLOC();
+ return DEREF;
+}
+
{operator} {
/*
* Check for embedded slash-star or dash-dash; those
@@ -955,7 +960,7 @@ other .
* that the "self" rule would have.
*/
if (nchars == 1 &&
- strchr(",()[].;:+-*/%^<>=", yytext[0]))
+ strchr(",()[].;:+-*/%^<>={}", yytext[0]))
return yytext[0];
/*
* Likewise, if what we have left is two chars, and
diff --git a/src/backend/utils/misc/Makefile b/src/backend/utils/misc/Makefile
index b362ae4377..124133e7dd 100644
--- a/src/backend/utils/misc/Makefile
+++ b/src/backend/utils/misc/Makefile
@@ -16,8 +16,11 @@ override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
OBJS = \
conffiles.o \
+ guc_composite.o \
guc.o \
guc-file.o \
+ guc_composite_gram.o\
+ guc_composite_scan.o\
guc_funcs.o \
guc_tables.o \
help_config.o \
@@ -42,5 +45,18 @@ endif
include $(top_srcdir)/src/backend/common.mk
+# See notes in src/backend/parser/Makefile about the following two rules
+guc_composite_gram.h: guc_composite_gram.c
+ touch $@
+
+guc_composite_gram.c: BISONFLAGS += -d
+
+# Force these dependencies to be known even without dependency info built:
+guc_composite_gram.o guc_composite_scanner.o: guc_composite_gram.h
+
+
clean:
rm -f guc-file.c
+ rm -f guc_composite_scan.c
+ rm -f guc_composite_gram.c
+ rm -f guc_composite_gram.h
diff --git a/src/backend/utils/misc/README b/src/backend/utils/misc/README
index 85d97d29b6..a660d890c5 100644
--- a/src/backend/utils/misc/README
+++ b/src/backend/utils/misc/README
@@ -292,3 +292,121 @@ worry about NULL values ever, the variable can be given a non-null static
initializer as well as a non-null boot_val. guc.c will overwrite the
static initializer pointer with a copy of the boot_val during
InitializeGUCOptions, but the variable will never contain a NULL.
+
+
+GUC composite type options
+--------------------------
+
+Composite types include structures, static and dynamic arrays.
+Composite types can have several levels of nesting.
+
+User interface:
+
+ To use the composite type options, you need to register the types
+ used in the system. To do this, add the type description to the guc_tables.c
+ file in the UserDefinedConfigureTypes array or use the DefineCustomCompositeType
+ function if you are creating a type from an extension. Note that the sequence
+ of fields in the type signature must exactly match the signature
+ of the structure in the C code.
+
+ type definition is a structure type_definition. See guc_tables.h
+ Main fields of the definition are type_name and signature.
+ The signature syntax is as follows:
+ "<type_1> <name_1>;<type_2> <name_2>; ... ; <type_K> <name_K>"
+ type_i - type of field
+ name_i - name of field
+
+ The field can have a type that is already registered in the system
+ (types that are in the array before) or array type of already registered types
+ Arrays syntax:
+ <type>[<length>] - static array
+ <type>[] - dynamic array
+
+ Representation in C code of dynamic array is a structure like
+ struct DynamicArray
+ {
+ void *data;
+ int size;
+ };
+ It is important to keep the order of the fields, because mapping takes place
+ according to the type signature and alignment rules of the C language.
+
+ After registering the composite type, you can use it when declaring
+ a new composite option in the ConfigureNamesComposite array.
+
+ Composite types in the user interface support the following syntax:
+ option_name = {field_name: field_value, ..., field_name: field_value}
+ option_name->field_name = field_value
+ option_name = [value, value, ..., value]
+ option_name = [<index>: value, ..., <index>: value]
+ The same syntax is used in psql in SET and SHOW commands.
+
+Example:
+
+ Consider example option in C code:
+
+ struct DynArrStr
+ {
+ char **data;
+ int size;
+ };
+
+ struct ExampleOptionType
+ {
+ int a;
+ struct DynArrStr b;
+ double c[3];
+ };
+
+ struct ExampleOptionType example_option;
+
+ To register new option in GUC follow instruction:
+
+ 1. Declare global structure for boot value:
+
+ struct ExampleOptionType example_option_boot;
+
+ 2. In guc_tables.c in UserDefinedConfigureTypes declare element
+
+ {
+ "example_type",
+ "int a; string[] b; real[3] c"
+ } /* other fields will be filled automatically */
+ Notice, that declarations in UserDefinedConfigureTypes
+ must be sorted with topological order
+
+ 3. In guc_tables.c in ConfigureNamesComposite declare element
+
+ {
+ {"example_option",...}, /* config_generic */
+ "example_type", /* option type */
+ &example_option,
+ &example_option_boot,
+ NULL, NULL, NULL /* hooks */
+ }
+ Option type is a type that declared in UserDefinedConfigureTypes or array type
+
+ For extensions use functions:
+ DefineCustomCompositeType - define new type
+ DefineCustomCompositeVariable - define new composite guc option
+
+Internals:
+
+ Composite types use incremental value setting semantics.
+ When setting a value, you can omit some fields of the structure/array,
+ then the unspecified fields will not change their values.
+ Maintaining this semantics for extensions required special processing
+ of placeholders for structural values. Instead of rewriting the value
+ of the field, we add it using ';'. This preserves the linear order in which
+ values are applied.
+
+ Since the logic of processing placeholders for composite types differs
+ from others, there is a need to distinguish between composite types and
+ scalar ones. To do this, it is forbidden to wrap a compound type value
+ in quotation marks. Inside, the system will add characters "->" to the
+ name of such a value, which only composite option can have.
+
+ In general, the processing of quotation marks for composite types differs
+ from scalar types, since string fields are wrapped in quotation marks
+ and the usual Deescape is not suitable for the entire structure.
+ Therefore, be careful when changing this logic.
diff --git a/src/backend/utils/misc/gen_guc_tables.pl b/src/backend/utils/misc/gen_guc_tables.pl
index bc8233f2d3..a612b2ddfd 100644
--- a/src/backend/utils/misc/gen_guc_tables.pl
+++ b/src/backend/utils/misc/gen_guc_tables.pl
@@ -25,7 +25,7 @@ my $parse = Catalog::ParseData($input_fname);
open my $ofh, '>', $output_fname or die;
print_boilerplate($ofh, $output_fname, 'GUC tables');
-foreach my $type (qw(bool int real string enum))
+foreach my $type (qw(bool int real string enum composite))
{
print_one_table($ofh, $type);
}
@@ -79,6 +79,8 @@ sub print_one_table
print $ofh "\n";
}
print $ofh "\t\t},\n";
+ printf $ofh "\t\t%s,\n", dquote($entry->{type_name})
+ if $entry->{type} eq 'composite';
print $ofh "\t\t&$entry->{variable},\n";
print $ofh "\t\t$entry->{boot_val},";
print $ofh " $entry->{min},"
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index 11a1e2a3f9..6f53b6f2ef 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -83,10 +83,13 @@ LETTER [A-Za-z_\200-\377]
LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
ID {LETTER}{LETTER_OR_DIGIT}*
-QUALIFIED_ID {ID}"."{ID}
+DEREF "->"
+INDEX "["{DIGIT}+"]"
+AID {ID}({DEREF}{ID}|{INDEX})*
+QUALIFIED_ID {ID}"."{AID}
-UNQUOTED_STRING {LETTER}({LETTER_OR_DIGIT}|[-._:/])*
STRING \'([^'\\\n]|\\.|\'\')*\'
+UNQUOTED_STRING (({LETTER}({LETTER_OR_DIGIT}|[-._:/])*)|(\{|\[)({LETTER_OR_DIGIT}|{STRING}|[-._:/\,\[\]\{\}\ \t\r\n])*(\}|\]))
%%
@@ -94,7 +97,7 @@ STRING \'([^'\\\n]|\\.|\'\')*\'
[ \t\r]+ /* eat whitespace */
#.* /* eat comment (.* matches anything until newline) */
-{ID} return GUC_ID;
+{AID} return GUC_ID;
{QUALIFIED_ID} return GUC_QUALIFIED_ID;
{STRING} return GUC_STRING;
{UNQUOTED_STRING} return GUC_UNQUOTED_STRING;
@@ -421,8 +424,28 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
if (token == GUC_STRING) /* strip quotes and escapes */
opt_value = DeescapeQuotedString(yytext);
else
+ {
opt_value = pstrdup(yytext);
+ if (token == GUC_UNQUOTED_STRING && (opt_value[0] == '{' || opt_value[0] == '['))
+ {
+ char *line_begin = opt_value;
+ /* mark name as composite */
+ int name_len = strlen(opt_name) + 3;
+ char *composite_name = (char *)palloc(name_len);
+ snprintf(composite_name, name_len, "%s->", opt_name);
+ pfree(opt_name);
+ opt_name = composite_name;
+
+ /* count lines for multi-line composite values */
+ while ((line_begin = strchr(line_begin, '\n')) != NULL)
+ {
+ ConfigFileLineno++;
+ line_begin++;
+ }
+ }
+ }
+
/* now we'd like an end of line, or possibly EOF */
token = yylex(scanner);
if (token != GUC_EOL)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 46fdefebe3..eac986a1c7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -35,6 +35,7 @@
#include "catalog/pg_authid.h"
#include "catalog/pg_parameter_acl.h"
#include "guc_internal.h"
+#include "guc_composite.h"
#include "libpq/pqformat.h"
#include "libpq/protocol.h"
#include "miscadmin.h"
@@ -61,18 +62,16 @@
#define CONFIG_EXEC_PARAMS_NEW "global/config_exec_params.new"
#endif
-/*
- * Precision with which REAL type guc values are to be printed for GUC
- * serialization.
- */
-#define REALTYPE_PRECISION 17
-
/*
* Safe search path when executing code as the table owner, such as during
* maintenance operations.
*/
#define GUC_SAFE_SEARCH_PATH "pg_catalog, pg_temp"
+#define is_marked_scalar(record,item) (record->status & GUC_IS_IN_FILE)\
+ && !strchr(item->name, '-')\
+ && !strchr(item->name, '[')
+
static int GUC_check_errcode_value;
static List *reserved_class_prefix = NIL;
@@ -270,6 +269,8 @@ static bool call_string_check_hook(struct config_string *conf, char **newval,
void **extra, GucSource source, int elevel);
static bool call_enum_check_hook(struct config_enum *conf, int *newval,
void **extra, GucSource source, int elevel);
+static bool call_composite_check_hook(struct config_composite *conf, void *newval,
+ void **extra, GucSource source, int elevel);
/*
@@ -401,8 +402,13 @@ ProcessConfigFileInternal(GucContext context, bool applySettings, int elevel)
if (record)
{
- /* If it's already marked, then this is a duplicate entry */
- if (record->status & GUC_IS_IN_FILE)
+ /*
+ * If it's already marked, then this is a duplicate entry However
+ * composite values are patches that could intersect or union like
+ * sets
+ */
+ if (is_marked_scalar(record, item)) /* do not optimize for
+ * composites */
{
/*
* Mark the earlier occurrence(s) as dead/ignorable. We could
@@ -741,6 +747,54 @@ set_string_field(struct config_string *conf, char **field, char *newval)
guc_free(oldval);
}
+
+/*
+ * Detect whether compositeval is referenced anywhere in a GUC struct item
+ */
+static bool
+composite_used(struct config_composite *conf, void *compositeval)
+{
+ GucStack *stack;
+
+ if (compositeval == conf->variable ||
+ compositeval == conf->reset_val ||
+ compositeval == conf->boot_val)
+ return true;
+
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (compositeval == stack->prior.val.compositeval ||
+ compositeval == stack->masked.val.compositeval)
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Assign value to a composite GUC option. Free the prior value if it's not
+ * referenced anywhere else in the option values (including stacked states).
+ * Set flag "value_on_heap" to true to allocate new value.
+ */
+static void
+set_composite(struct config_composite *conf, void **field, void *newval, bool value_on_heap)
+{
+ void *oldval = *field;
+
+ /* Do the assignment */
+ if (value_on_heap)
+ *field = composite_dup(newval, conf->type_name);
+ else
+ {
+ free_composite_impl(*field, conf->type_name);
+ composite_dup_impl(*field, newval, conf->type_name);
+ }
+
+ /* Free old value if it's not NULL and isn't referenced anymore */
+ if (value_on_heap && oldval && !composite_used(conf, oldval))
+ free_composite(oldval, conf->type_name);
+}
+
/*
* Detect whether an "extra" struct is referenced anywhere in a GUC item
*/
@@ -773,6 +827,10 @@ extra_field_used(struct config_generic *gconf, void *extra)
if (extra == ((struct config_enum *) gconf)->reset_extra)
return true;
break;
+ case PGC_COMPOSITE:
+ if (extra == ((struct config_composite *) gconf)->reset_extra)
+ return true;
+ break;
}
for (stack = gconf->stack; stack; stack = stack->prev)
{
@@ -835,6 +893,14 @@ set_stack_value(struct config_generic *gconf, config_var_value *val)
val->val.enumval =
*((struct config_enum *) gconf)->variable;
break;
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+
+ set_composite((struct config_composite *) gconf,
+ &(val->val.compositeval),
+ ((struct config_composite *) gconf)->variable, value_on_heap);
+ }
}
set_extra_field(gconf, &(val->extra), gconf->extra);
}
@@ -859,6 +925,15 @@ discard_stack_value(struct config_generic *gconf, config_var_value *val)
&(val->val.stringval),
NULL);
break;
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+
+ set_composite((struct config_composite *) gconf,
+ &(val->val.compositeval),
+ NULL, value_on_heap);
+ break;
+ }
}
set_extra_field(gconf, &(val->extra), NULL);
}
@@ -907,6 +982,12 @@ build_guc_variables(void)
int num_vars = 0;
HASHCTL hash_ctl;
GUCHashEntry *hentry;
+
+ int size_types;
+ int num_types = 0;
+ HASHCTL types_hash_ctl;
+ OptionTypeHashEntry *type_hentry;
+
bool found;
int i;
@@ -918,6 +999,12 @@ build_guc_variables(void)
"GUCMemoryContext",
ALLOCSET_DEFAULT_SIZES);
+ /*
+ * Count all type defintions
+ */
+ for (i = 0; UserDefinedConfigureTypes[i].type_name; i++)
+ num_types++;
+
/*
* Count all the built-in variables, and set their vartypes correctly.
*/
@@ -962,9 +1049,46 @@ build_guc_variables(void)
num_vars++;
}
+ for (i = 0; ConfigureNamesComposite[i].gen.name; i++)
+ {
+ struct config_composite *conf = &ConfigureNamesComposite[i];
+
+ conf->gen.vartype = PGC_COMPOSITE;
+ num_vars++;
+ }
+
/*
- * Create hash table with 20% slack
+ * Create hash tables with 20% slack
*/
+
+ size_types = num_types + num_types / 4;
+ types_hash_ctl.keysize = sizeof(char *);
+ types_hash_ctl.entrysize = sizeof(OptionTypeHashEntry);
+ types_hash_ctl.hash = guc_name_hash;
+ types_hash_ctl.match = guc_name_match;
+ types_hash_ctl.hcxt = GUCMemoryContext;
+ guc_types_hashtab = hash_create("GUC user types hash table",
+ size_types,
+ &types_hash_ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT);
+
+ for (i = 0; UserDefinedConfigureTypes[i].type_name; i++)
+ {
+ struct type_definition *type_definition = &UserDefinedConfigureTypes[i];
+
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab,
+ &type_definition->type_name,
+ HASH_ENTER,
+ &found);
+ Assert(!found);
+ type_hentry->definition = type_definition;
+
+ if (!is_scalar_type(type_definition->type_name))
+ init_type_definition(type_definition);
+ }
+
+ Assert(num_types == hash_get_num_entries(guc_types_hashtab));
+
size_vars = num_vars + num_vars / 4;
hash_ctl.keysize = sizeof(char *);
@@ -1037,6 +1161,17 @@ build_guc_variables(void)
hentry->gucvar = gucvar;
}
+ for (i = 0; ConfigureNamesComposite[i].gen.name; i++)
+ {
+ struct config_generic *gucvar = &ConfigureNamesComposite[i].gen;
+ hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+ &gucvar->name,
+ HASH_ENTER,
+ &found);
+ Assert(!found);
+ hentry->gucvar = gucvar;
+ }
+
Assert(num_vars == hash_get_num_entries(guc_hashtab));
}
@@ -1078,13 +1213,15 @@ valid_custom_variable_name(const char *name)
{
bool saw_sep = false;
bool name_start = true;
+ bool is_field = false;
+ const char *p;
- for (const char *p = name; *p; p++)
+ for (p = name; *p; p++)
{
if (*p == GUC_QUALIFIER_SEPARATOR)
{
- if (name_start)
- return false; /* empty name component */
+ if (name_start || is_field)
+ return false; /* empty name component or field of struct */
saw_sep = true;
name_start = true;
}
@@ -1097,10 +1234,40 @@ valid_custom_variable_name(const char *name)
}
else if (!name_start && strchr("0123456789$", *p) != NULL)
/* okay as non-first character */ ;
+ else if (*p == '-')
+ {
+ if (!name_start && *(p + 1) == '>')
+ {
+ name_start = true;
+ is_field = true;
+ p++;
+ }
+ else
+ return false;
+ }
+ else if (!name_start && *p == '[')
+ {
+ p++;
+ while (strchr("0123456789 ", *p) != NULL)
+ p++;
+
+ if ((*p == ']' && !*(p + 1)) || *(p + 1) == '-')
+ is_field = true;
+ else
+ return false;
+ }
else
return false;
}
- if (name_start)
+
+ /*
+ * Composite's name could be ended with '->', we should ignore last
+ * dereference This dirty hack is used to see difference between composite
+ * names and other GUCs It is important in case when we write placeholder
+ * for composite, cause rule of writing placeholders for composite and
+ * for other types are not the same
+ */
+ if (name_start && *(p - 1) != '>')
return false; /* empty name component */
/* OK if we found at least one separator */
return saw_sep;
@@ -1238,6 +1405,10 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
{
GUCHashEntry *hentry;
int i;
+ char *field_path_start = NULL;
+ char *first_bracket = NULL;
+ char *first_minus = NULL;
+ char *struct_name = NULL;
Assert(name);
@@ -1249,6 +1420,37 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
if (hentry)
return hentry->gucvar;
+ /*
+ * If value is a field of composite, then it's name is a path that
+ * consists of fields names, dereferences "->" and indexes [<number>]
+ */
+ first_bracket = strchr(name, '[');
+ first_minus = strchr(name, '-');
+
+ if (first_bracket != NULL || first_minus != NULL)
+ {
+ /* take first dereference */
+ if (((first_bracket < first_minus) && first_bracket != NULL) || first_minus == NULL)
+ field_path_start = first_bracket;
+ else
+ field_path_start = first_minus;
+
+ struct_name = guc_malloc(ERROR, (field_path_start - name + 1));
+ strncpy(struct_name, name, (field_path_start - name));
+ struct_name[field_path_start - name] = 0;
+
+ hentry = (GUCHashEntry *) hash_search(guc_hashtab,
+ &struct_name,
+ HASH_FIND,
+ NULL);
+
+ if (hentry)
+ {
+ guc_free(struct_name);
+ return hentry->gucvar;
+ }
+ }
+
/*
* See if the name is an obsolete name for a variable. We assume that the
* set of supported old names is short enough that a brute-force search is
@@ -1267,11 +1469,24 @@ find_option(const char *name, bool create_placeholders, bool skip_errors,
* Check if the name is valid, and if so, add a placeholder.
*/
if (assignable_custom_variable_name(name, skip_errors, elevel))
+ {
+ if (field_path_start)
+ {
+ struct config_generic *result;
+
+ result = add_placeholder_variable(struct_name, elevel);
+ guc_free(struct_name);
+
+ return result;
+ }
+
return add_placeholder_variable(name, elevel);
+ }
else
return NULL; /* error message, if any, already emitted */
}
+ guc_free(struct_name);
/* Unknown name and we're not supposed to make a placeholder */
if (!skip_errors)
ereport(elevel,
@@ -1431,6 +1646,7 @@ check_GUC_name_for_parameter_acl(const char *name)
* real - can be 0.0, otherwise must be same as the boot_val
* string - can be NULL, otherwise must be strcmp equal to the boot_val
* enum - must be same as the boot_val
+ * struct - can be NULL, otherwise must be bitwise equal to the boot_val
*/
#ifdef USE_ASSERT_CHECKING
static bool
@@ -1499,6 +1715,38 @@ check_GUC_init(struct config_generic *gconf)
conf->gen.name, conf->boot_val, *conf->variable);
return false;
}
+ break;
+ }
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ bool is_null = true;
+ int type_size = get_composite_size(conf->type_name);
+
+ for (int i = 0; i < type_size; ++i)
+ {
+ if (*((char *) (conf->variable) + i) != 0)
+ {
+ is_null = false;
+ break;
+ }
+ }
+
+ if (!is_null && composite_cmp(conf->variable, conf->boot_val, conf->type_name))
+ {
+ bool not_write_to_file = false;
+ char *boot_str = composite_to_str(conf->boot_val, conf->type_name, not_write_to_file);
+ char *var_str = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ elog(LOG, "GUC (PGC_COMPOSITE %s) %s, boot_val=%s, C-var=%s",
+ conf->type_name, conf->gen.name, boot_str, var_str);
+
+ guc_free(boot_str);
+ guc_free(var_str);
+
+ return false;
+ }
+
break;
}
}
@@ -1747,6 +1995,65 @@ InitializeOneGUCOption(struct config_generic *gconf)
conf->assign_hook(newval, extra);
*conf->variable = conf->reset_val = newval;
conf->gen.extra = conf->reset_extra = extra;
+ break;
+ }
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ void *newval;
+ void *extra = NULL;
+ void *var_buffer = NULL;
+ int valsize;
+
+ if (!conf->type_name && !conf->definition)
+ {
+ elog(FATAL, "failed to initialize %s. The type not specified",
+ conf->gen.name);
+
+ break;
+ }
+
+ if (!conf->type_name)
+ conf->type_name = conf->definition->type_name;
+
+ if (!conf->definition
+ && !is_static_array_type(conf->type_name)
+ && !is_dynamic_array_type(conf->type_name))
+ {
+ conf->definition = get_type_definition(conf->type_name);
+
+ if (!conf->definition)
+ {
+ elog(FATAL, "failed to initialize %s. Undefined type %s",
+ conf->gen.name, conf->type_name);
+
+ break;
+ }
+ }
+
+ /* non-NULL boot_val must always get compositedup'd */
+ if (conf->boot_val != NULL)
+ newval = composite_dup(conf->boot_val, conf->type_name);
+ else
+ newval = NULL;
+
+ if (!call_composite_check_hook(conf, newval, &extra,
+ PGC_S_DEFAULT, LOG))
+ elog(FATAL, "failed to initialize %s to %s",
+ conf->gen.name, newval ? composite_to_str(newval, conf->type_name, false) : "");
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, extra);
+
+ conf->reset_val = newval;
+
+ valsize = get_composite_size(conf->type_name);
+ var_buffer = composite_dup(newval, conf->type_name);
+ memcpy(conf->variable, var_buffer, valsize);
+ guc_free(var_buffer);
+
+ conf->gen.extra = conf->reset_extra = extra;
+
break;
}
}
@@ -2091,6 +2398,23 @@ ResetAllOptions(void)
conf->reset_extra);
break;
}
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+ int valsize;
+
+ if (conf->assign_hook)
+ conf->assign_hook(conf->reset_val,
+ conf->reset_extra);
+
+ valsize = get_composite_size(conf->type_name);
+ memcpy(conf->variable, conf->reset_val, valsize);
+
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ conf->reset_extra);
+
+ break;
+ }
}
set_guc_source(gconf, gconf->reset_source);
@@ -2505,6 +2829,37 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
}
break;
}
+ case PGC_COMPOSITE:
+ {
+ bool value_on_heap = true;
+ struct config_composite *conf = (struct config_composite *) gconf;
+ void *newval = newvalue.val.compositeval;
+ void *newextra = newvalue.extra;
+
+ if (composite_cmp(conf->variable, newval, conf->type_name) ||
+ conf->gen.extra != newextra)
+ {
+ bool value_not_on_heap = false;
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, newextra);
+
+ set_composite(conf, &conf->variable, newval, value_not_on_heap);
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ changed = true;
+ }
+
+ /*
+ * Release stacked values if not used anymore. We
+ * could use discard_stack_value() here, but since
+ * we have type-specific code anyway, might as
+ * well inline it.
+ */
+ set_composite(conf, &stack->prior.val.compositeval, NULL, value_on_heap);
+ set_composite(conf, &stack->masked.val.compositeval, NULL, value_on_heap);
+ break;
+ }
}
/*
@@ -3296,6 +3651,27 @@ parse_and_validate_value(struct config_generic *record,
return false;
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+ const char *hintmsg;
+
+ if (!parse_composite(value, conf->type_name, &newval->compositeval, conf->variable,
+ conf->gen.flags, &hintmsg))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for parameter \"%s\": \"%s\"",
+ conf->gen.name, value),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ return false;
+ }
+
+ if (!call_composite_check_hook(conf, newval->compositeval, newextra,
+ source, elevel))
+ return false;
+ }
+ break;
}
return true;
@@ -4012,9 +4388,76 @@ set_config_with_handle(const char *name, config_handle *handle,
if (value)
{
- if (!parse_and_validate_value(record, value,
- source, elevel,
- &newval_union, &newextra))
+ /* prepare placeholder for structure */
+ bool is_placeholder = false;
+ bool composite_value = false;
+ bool check_parsing;
+ char *prepared_strval = (char *) value; /* be careful with free */
+
+ /* check if string is a field of composite object or it */
+ if (strchr(name, '-') || strchr(name, '['))
+ is_placeholder = true;
+
+ if (suffix_is_arrow(name))
+ composite_value = true;
+
+ if (is_placeholder)
+ {
+ char *list;
+ char *old_list;
+ int list_length;
+
+ /*
+ * If value is a composite, name that ended with "->"
+ * All other values are scalar, they must be escaped
+ */
+ if (!composite_value)
+ {
+ char *escaped = escape_single_quotes_ascii(value);
+ int quoted_length = strlen(escaped) + 3;
+ char *quoted = guc_malloc(ERROR, quoted_length);
+
+ snprintf(quoted, quoted_length, "\'%s\'", escaped);
+
+ prepared_strval = convert_path_to_composite_value(name, quoted);
+
+ free(escaped);
+ guc_free(quoted);
+ }
+ else
+ prepared_strval = convert_path_to_composite_value(name, value);
+
+ /* get list from current value */
+ old_list = *(conf->variable);
+
+ if (old_list == NULL)
+ old_list = "";
+
+ /* Add value to placeholder patch list */
+ if (strlen(old_list) == 0)
+ {
+ list_length= strlen(prepared_strval) + 1;
+ list = guc_malloc(ERROR, list_length);
+ snprintf(list, list_length, "%s", prepared_strval);
+ }
+ else
+ {
+ list_length = strlen(old_list) + strlen(prepared_strval) + 2;
+ list = guc_malloc(ERROR, list_length);
+ snprintf(list, list_length, "%s;%s", old_list, prepared_strval);
+ }
+
+ guc_free(prepared_strval);
+ prepared_strval = list;
+ }
+
+ check_parsing = parse_and_validate_value(record, prepared_strval, source,
+ elevel, &newval_union, &newextra);
+
+ if (prepared_strval != value)
+ guc_free(prepared_strval);
+
+ if (!check_parsing)
return 0;
}
else if (source == PGC_S_DEFAULT)
@@ -4268,33 +4711,172 @@ set_config_with_handle(const char *name, config_handle *handle,
#undef newval
}
- }
-
- if (changeVal && (record->flags & GUC_REPORT) &&
- !(record->status & GUC_NEEDS_REPORT))
- {
- record->status |= GUC_NEEDS_REPORT;
- slist_push_head(&guc_report_list, &record->report_link);
- }
-
- return changeVal ? 1 : -1;
-}
-
-
-/*
- * Retrieve a config_handle for the given name, suitable for calling
- * set_config_with_handle(). Only return handle to permanent GUC.
- */
-config_handle *
-get_config_handle(const char *name)
-{
- struct config_generic *gen = find_option(name, false, false, 0);
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+ bool parse_result;
- if (gen && ((gen->flags & GUC_CUSTOM_PLACEHOLDER) == 0))
- return gen;
+#define newval (newval_union.compositeval)
+ if (value)
+ {
+ const char *str_val = (const char *) normalize_composite_value(name, value);
- return NULL;
-}
+ parse_result = parse_and_validate_value(record, str_val, source, elevel,
+ &newval_union, &newextra);
+
+ guc_free((void *) str_val);
+
+ if (!parse_result)
+ return 0;
+ }
+ else if (source == PGC_S_DEFAULT)
+ {
+ /* non-NULL boot_val must be compositedup'd */
+ if (conf->boot_val != NULL)
+ {
+ newval = composite_dup(conf->boot_val, conf->type_name);
+ if (newval == NULL)
+ return 0;
+ }
+ else
+ newval = NULL;
+
+ if (!call_composite_check_hook(conf, newval, &newextra,
+ source, elevel))
+ {
+ guc_free(newval);
+ return 0;
+ }
+ }
+ else
+ {
+ /*
+ * composite_dup not needed, since reset_val is already
+ * under guc.c's control
+ */
+ newval = conf->reset_val;
+ newextra = conf->reset_extra;
+ source = conf->gen.reset_source;
+ context = conf->gen.reset_scontext;
+ srole = conf->gen.reset_srole;
+ }
+
+ if (prohibitValueChange)
+ {
+ bool newval_different;
+
+ /* newval shouldn't be NULL, so we're a bit sloppy here */
+ newval_different = (conf->variable == NULL ||
+ newval == NULL ||
+ composite_cmp(conf->variable, newval, conf->type_name) != 0);
+
+ /* Release newval, unless it's reset_val */
+ if (newval && !composite_used(conf, newval))
+ free_composite(newval, conf->type_name);
+ /* Release newextra, unless it's reset_extra */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ guc_free(newextra);
+
+ if (newval_different)
+ {
+ record->status |= GUC_PENDING_RESTART;
+ ereport(elevel,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("parameter \"%s\" cannot be changed without restarting the server",
+ conf->gen.name)));
+ return 0;
+ }
+ record->status &= ~GUC_PENDING_RESTART;
+ return -1;
+ }
+
+ if (changeVal)
+ {
+ bool value_on_heap = false;
+
+ /* Save old value to support transaction abort */
+ if (!makeDefault)
+ push_old_value(&conf->gen, action);
+
+ if (conf->assign_hook)
+ conf->assign_hook(newval, newextra);
+ set_composite(conf, &conf->variable, newval, value_on_heap);
+ set_extra_field(&conf->gen, &conf->gen.extra,
+ newextra);
+ set_guc_source(&conf->gen, source);
+ conf->gen.scontext = context;
+ conf->gen.srole = srole;
+ }
+
+ if (makeDefault)
+ {
+ bool value_on_heap = true;
+ GucStack *stack;
+
+ if (conf->gen.reset_source <= source)
+ {
+ set_composite(conf, &conf->reset_val, newval, value_on_heap);
+ set_extra_field(&conf->gen, &conf->reset_extra,
+ newextra);
+ conf->gen.reset_source = source;
+ conf->gen.reset_scontext = context;
+ conf->gen.reset_srole = srole;
+ }
+ for (stack = conf->gen.stack; stack; stack = stack->prev)
+ {
+ if (stack->source <= source)
+ {
+ set_composite(conf, &stack->prior.val.compositeval,
+ newval, value_on_heap);
+ set_extra_field(&conf->gen, &stack->prior.extra,
+ newextra);
+ stack->source = source;
+ stack->scontext = context;
+ stack->srole = srole;
+ }
+ }
+ }
+
+
+ /* Perhaps we didn't install newval anywhere */
+ if (newval && !composite_used(conf, newval))
+ free_composite(newval, conf->type_name);
+
+ /* Perhaps we didn't install newextra anywhere */
+ if (newextra && !extra_field_used(&conf->gen, newextra))
+ {
+ guc_free(newextra);
+ break;
+ }
+#undef newval
+ }
+ }
+
+ if (changeVal && (record->flags & GUC_REPORT) &&
+ !(record->status & GUC_NEEDS_REPORT))
+ {
+ record->status |= GUC_NEEDS_REPORT;
+ slist_push_head(&guc_report_list, &record->report_link);
+ }
+
+ return changeVal ? 1 : -1;
+}
+
+
+/*
+ * Retrieve a config_handle for the given name, suitable for calling
+ * set_config_with_handle(). Only return handle to permanent GUC.
+ */
+config_handle *
+get_config_handle(const char *name)
+{
+ struct config_generic *gen = find_option(name, false, false, 0);
+
+ if (gen && ((gen->flags & GUC_CUSTOM_PLACEHOLDER) == 0))
+ return gen;
+
+ return NULL;
+}
/*
@@ -4395,6 +4977,14 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
case PGC_ENUM:
return config_enum_lookup_by_value((struct config_enum *) record,
*((struct config_enum *) record)->variable);
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ void *var = ((struct config_composite *) record)->variable;
+ const char *var_type = ((struct config_composite *) record)->type_name;
+
+ return var ? composite_to_str(var, var_type, not_write_to_file) : "nil";
+ }
}
return NULL;
}
@@ -4443,6 +5033,15 @@ GetConfigOptionResetString(const char *name)
case PGC_ENUM:
return config_enum_lookup_by_value((struct config_enum *) record,
((struct config_enum *) record)->reset_val);
+
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ void *val = ((struct config_composite *) record)->reset_val;
+ const char *val_type = ((struct config_composite *) record)->type_name;
+
+ return val ? composite_to_str(val, val_type, not_write_to_file) : "nil";
+ }
}
return NULL;
}
@@ -4493,25 +5092,52 @@ write_auto_conf_file(int fd, const char *filename, ConfigVariable *head)
errmsg("could not write to file \"%s\": %m", filename)));
}
- /* Emit each parameter, properly quoting the value */
+ /*
+ * Emit each parameter, quoting the value except when the option has a
+ * composite value
+ */
for (item = head; item != NULL; item = item->next)
{
+ bool is_struct = false;
char *escaped;
resetStringInfo(&buf);
- appendStringInfoString(&buf, item->name);
- appendStringInfoString(&buf, " = '");
+ /*
+ * Strutures names could be ended with "->" only in internal
+ * representation. So delete this suffix for user interface
+ */
+ if (item->name[strlen(item->name) - 2] == '-')
+ {
+ item->name[strlen(item->name) - 2] = 0;
+ appendStringInfoString(&buf, item->name);
+ is_struct = true;
+ item->name[strlen(item->name) - 2] = '-';
+ }
+ else
+ appendStringInfoString(&buf, item->name);
+ appendStringInfoString(&buf, " = ");
- escaped = escape_single_quotes_ascii(item->value);
- if (!escaped)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of memory")));
- appendStringInfoString(&buf, escaped);
- free(escaped);
+ /*
+ * Append quotes for all scalar types But do not append qoutes for
+ * structure values
+ */
+ if (!is_struct)
+ {
+ appendStringInfoString(&buf, "\'");
+ escaped = escape_single_quotes_ascii(item->value);
+ if (!escaped)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of memory")));
+ appendStringInfoString(&buf, escaped);
+ free(escaped);
+ appendStringInfoString(&buf, "\'");
+ }
+ else
+ appendStringInfoString(&buf, item->value);
- appendStringInfoString(&buf, "'\n");
+ appendStringInfoString(&buf, "\n");
errno = 0;
if (write(fd, buf.data, buf.len) != buf.len)
@@ -4546,6 +5172,9 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
*next,
*prev = NULL;
+ char *short_name = tokenize_field_path(pstrdup(name));
+ bool is_struct = strchr(name, '-');
+
/*
* Remove any existing match(es) for "name". Normally there'd be at most
* one, but if external tools have modified the config file, there could
@@ -4553,8 +5182,11 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
*/
for (item = *head_p; item != NULL; item = next)
{
+ char *tokenized_item_name = tokenize_field_path(pstrdup(item->name));
+
next = item->next;
- if (guc_name_compare(item->name, name) == 0)
+
+ if (guc_name_compare(tokenized_item_name, short_name) == 0)
{
/* found a match, delete it */
if (prev)
@@ -4571,15 +5203,23 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
}
else
prev = item;
+ pfree(tokenized_item_name);
}
/* Done if we're trying to delete it */
if (value == NULL)
+ {
+ pfree(short_name);
return;
+ }
/* OK, append a new entry */
item = palloc(sizeof *item);
- item->name = pstrdup(name);
+ if (is_struct)
+ item->name = pstrdup(name);
+ else
+ item->name = pstrdup(short_name);
+
item->value = pstrdup(value);
item->errmsg = NULL;
item->filename = pstrdup(""); /* new item has no location */
@@ -4593,6 +5233,7 @@ replace_auto_config_value(ConfigVariable **head_p, ConfigVariable **tail_p,
else
(*tail_p)->next = item;
*tail_p = item;
+ pfree(short_name);
}
@@ -4633,6 +5274,17 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
switch (altersysstmt->setstmt->kind)
{
case VAR_SET_VALUE:
+ if (stmt_has_serialized_composite(altersysstmt->setstmt))
+ {
+ /* replace name for structs */
+ int composite_name_len = strlen(name) + 3;
+ char *composite_name = (char *) palloc(composite_name_len);
+
+ snprintf(composite_name, composite_name_len, "%s->", name);
+ pfree(name);
+ name = composite_name;
+ }
+
value = ExtractSetVariableArgs(altersysstmt->setstmt);
break;
@@ -4705,8 +5357,15 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
{
union config_var_val newval;
void *newextra = NULL;
+ char *prepared_value = value;
- if (!parse_and_validate_value(record, value,
+ /*
+ * For struct values we should convert path
+ */
+ if (record->vartype == PGC_COMPOSITE)
+ prepared_value = normalize_composite_value(name, value);
+
+ if (!parse_and_validate_value(record, prepared_value,
PGC_S_FILE, ERROR,
&newval, &newextra))
ereport(ERROR,
@@ -4714,8 +5373,74 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
errmsg("invalid value for parameter \"%s\": \"%s\"",
name, value)));
+ /*
+ * Each composite placeholder has -> in name For placeholder
+ * we using rules from set_config_with_handle Also cut off
+ * suffix of name
+ */
+ if (record->vartype == PGC_STRING && strchr(name, '-'))
+ {
+ struct config_string *conf = (struct config_string *) record;
+
+ if (value)
+ {
+ char *prepared = normalize_composite_value(name, value);
+ char *list;
+ char *old_list = *conf->variable ? *conf->variable : "";
+ int list_len = strlen(old_list) + strlen(prepared) + 2;
+
+ /* Add value to placeholder patch list */
+ list = guc_malloc(ERROR, list_len);
+ snprintf(list, list_len, "%s%s;", old_list, prepared);
+
+ pfree(value);
+ value = pstrdup(list);
+ guc_free(list);
+ guc_free(prepared);
+ }
+ tokenize_field_path(name);
+ }
+
if (record->vartype == PGC_STRING && newval.stringval != NULL)
+ {
guc_free(newval.stringval);
+ }
+
+ /*
+ * For struct we replace value with serialized parsed value
+ * Notice that patch is applied to current value
+ */
+ if (record->vartype == PGC_COMPOSITE)
+ {
+ char *serial_struct = NULL;
+ char *composite_name;
+ int composite_name_length;
+ bool write_to_file = true;
+
+ pfree(value);
+ serial_struct = composite_to_str(newval.compositeval, ((struct config_composite *) record)->type_name, write_to_file);
+ if (serial_struct == NULL)
+ value = NULL;
+ else
+ {
+ value = pstrdup(serial_struct);
+ guc_free(serial_struct);
+ }
+ free_composite(newval.compositeval, ((struct config_composite *) record)->type_name);
+
+ /*
+ * replace name with struct name + "->"" We need suffix to
+ * detect structure in replace_auto_config_value and
+ * write_auto_conf_file
+ */
+ name = tokenize_field_path(name);
+ composite_name_length = strlen(name) + 3;
+ composite_name = (char *) palloc(composite_name_length);
+
+ snprintf(composite_name, composite_name_length, "%s->", name);
+ pfree(name);
+ name = composite_name;
+ }
guc_free(newextra);
}
}
@@ -4734,6 +5459,25 @@ AlterSystemSetConfigFile(AlterSystemStmt *altersysstmt)
*/
if (value || !valid_custom_variable_name(name))
(void) assignable_custom_variable_name(name, false, ERROR);
+
+ /*
+ * If option is composite, we can understand it with "->" or "[]"
+ * in name
+ */
+ if (strchr(name, '-') || strchr(name, '['))
+ {
+ char *prepared = normalize_composite_value(name, value);
+
+ pfree(value);
+ value = pstrdup(prepared);
+ guc_free(prepared);
+
+ /*
+ * prepare name to replace_auto_config_value We set -> at the
+ * end of name to distinguish placeholders for composites
+ */
+ tokenize_field_path(name);
+ }
}
/*
@@ -4944,6 +5688,15 @@ define_custom_variable(struct config_generic *variable)
const char *name = variable->name;
GUCHashEntry *hentry;
struct config_string *pHolder;
+ char *name_with_suffix = (char *) name;
+
+ if (variable->vartype == PGC_COMPOSITE)
+ {
+ int name_with_suffix_len = strlen(name) + 3;
+
+ name_with_suffix = guc_malloc(ERROR, name_with_suffix_len);
+ snprintf(name_with_suffix, name_with_suffix_len, "%s->", name);
+ }
/* Check mapping between initial and default value */
Assert(check_GUC_init(variable));
@@ -5009,7 +5762,7 @@ define_custom_variable(struct config_generic *variable)
/* First, apply the reset value if any */
if (pHolder->reset_val)
- (void) set_config_option_ext(name, pHolder->reset_val,
+ (void) set_config_option_ext(name_with_suffix, pHolder->reset_val,
pHolder->gen.reset_scontext,
pHolder->gen.reset_source,
pHolder->gen.reset_srole,
@@ -5030,6 +5783,9 @@ define_custom_variable(struct config_generic *variable)
/* Now we can free the no-longer-referenced placeholder variable */
free_placeholder(pHolder);
+
+ if (name_with_suffix != name)
+ guc_free(name_with_suffix);
}
/*
@@ -5049,6 +5805,15 @@ reapply_stacked_values(struct config_generic *variable,
{
const char *name = variable->name;
GucStack *oldvarstack = variable->stack;
+ char *name_with_suffix = (char *) name;
+
+ if (variable->vartype == PGC_COMPOSITE)
+ {
+ int name_with_suffix_len = strlen(name) + 3;
+
+ name_with_suffix = guc_malloc(ERROR, name_with_suffix_len);
+ snprintf(name_with_suffix, name_with_suffix_len, "%s->", name);
+ }
if (stack != NULL)
{
@@ -5061,21 +5826,21 @@ reapply_stacked_values(struct config_generic *variable,
switch (stack->state)
{
case GUC_SAVE:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SAVE, true,
WARNING, false);
break;
case GUC_SET:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SET, true,
WARNING, false);
break;
case GUC_LOCAL:
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_LOCAL, true,
WARNING, false);
@@ -5083,14 +5848,14 @@ reapply_stacked_values(struct config_generic *variable,
case GUC_SET_LOCAL:
/* first, apply the masked value as SET */
- (void) set_config_option_ext(name, stack->masked.val.stringval,
+ (void) set_config_option_ext(name_with_suffix, stack->masked.val.stringval,
stack->masked_scontext,
PGC_S_SESSION,
stack->masked_srole,
GUC_ACTION_SET, true,
WARNING, false);
/* then apply the current value as LOCAL */
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_LOCAL, true,
WARNING, false);
@@ -5116,7 +5881,7 @@ reapply_stacked_values(struct config_generic *variable,
cursource != pHolder->gen.reset_source ||
cursrole != pHolder->gen.reset_srole)
{
- (void) set_config_option_ext(name, curvalue,
+ (void) set_config_option_ext(name_with_suffix, curvalue,
curscontext, cursource, cursrole,
GUC_ACTION_SET, true, WARNING, false);
if (variable->stack != NULL)
@@ -5126,6 +5891,9 @@ reapply_stacked_values(struct config_generic *variable,
}
}
}
+
+ if (name != name_with_suffix)
+ guc_free(name_with_suffix);
}
/*
@@ -5289,6 +6057,66 @@ DefineCustomEnumVariable(const char *name,
define_custom_variable(&var->gen);
}
+void
+DefineCustomCompositeVariable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ const char *type_name,
+ void *valueAddr,
+ const void *bootValueAddr,
+ GucContext context,
+ int flags,
+ GucCompositeCheckHook check_hook,
+ GucCompositeAssignHook assign_hook,
+ GucShowHook show_hook)
+{
+ struct config_composite *var;
+ struct type_definition *type_definition = NULL;
+
+ if (!is_static_array_type(type_name) && !is_dynamic_array_type(type_name))
+ {
+ type_definition = get_type_definition(type_name);
+ if (type_definition == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("option %s has undefined type %s", name, type_name)));
+ }
+
+ var = (struct config_composite *)
+ init_custom_variable(name, short_desc, long_desc, context, flags,
+ PGC_COMPOSITE, sizeof(struct config_composite));
+ var->variable = valueAddr;
+ var->type_name = type_name;
+ var->boot_val = bootValueAddr;
+ var->check_hook = check_hook;
+ var->assign_hook = assign_hook;
+ var->show_hook = show_hook;
+ var->definition = type_definition;
+ define_custom_variable(&var->gen);
+}
+
+void
+DefineCustomCompositeType(const char *type_name, const char *signature)
+{
+ bool found;
+ OptionTypeHashEntry *type_hentry;
+ struct type_definition *type_definition = (struct type_definition *) guc_malloc(ERROR, sizeof(struct type_definition));
+
+ memset(type_definition, 0, sizeof(struct type_definition));
+ type_definition->type_name = type_name;
+ type_definition->signature = signature;
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab,
+ &type_definition->type_name,
+ HASH_ENTER,
+ &found);
+
+ Assert(!found);
+ type_hentry->definition = type_definition;
+
+ if (!is_scalar_type(type_definition->type_name))
+ init_type_definition(type_definition);
+}
+
/*
* Mark the given GUC prefix as "reserved".
*
@@ -5429,6 +6257,22 @@ get_explain_guc_options(int *num)
modified = (lconf->boot_val != *(lconf->variable));
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *lconf = (struct config_composite *) conf;
+
+ if (lconf->boot_val == NULL &&
+ lconf->variable == NULL)
+ modified = false;
+ else if (lconf->boot_val == NULL ||
+ lconf->variable == NULL)
+ modified = true;
+ else
+ {
+ modified = (composite_cmp(lconf->boot_val, lconf->variable, lconf->type_name) != 0);
+ }
+ }
+ break;
default:
elog(ERROR, "unexpected GUC type: %d", conf->vartype);
@@ -5454,6 +6298,8 @@ char *
GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
{
struct config_generic *record;
+ char *first_minus = strchr(name, '-');
+ char *first_bracket = strchr(name, '[');
record = find_option(name, false, missing_ok, ERROR);
if (record == NULL)
@@ -5473,6 +6319,39 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
if (varname)
*varname = record->name;
+ /*
+ * For structure fields we use custom show function. yes it's so sloppy
+ * but SHowGucOption can't use string as parameter
+ */
+ if (first_minus || first_bracket)
+ {
+ void *structp = NULL;
+ char *field_type;
+ char *str_opt;
+ char *result;
+ struct config_composite *conf = (struct config_composite *) record;
+
+ if (conf->show_hook)
+ result = pstrdup(conf->show_hook());
+ else
+ {
+ bool not_write_to_file = false;
+
+ /* find start of structure and convert it to struct */
+ structp = get_nested_field_ptr(conf->variable, conf->type_name, name);
+ field_type = get_nested_field_type_name(conf->type_name, name);
+ str_opt = composite_to_str(structp, field_type, not_write_to_file);
+
+ /* copy result */
+ result = pstrdup(str_opt);
+
+ /* free work structures */
+ guc_free(field_type);
+ guc_free(str_opt);
+ }
+ return result;
+ }
+
return ShowGUCOption(record, true);
}
@@ -5580,6 +6459,27 @@ ShowGUCOption(struct config_generic *record, bool use_units)
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) record;
+
+ if (conf->show_hook)
+ val = conf->show_hook();
+ else if (conf->variable)
+ {
+ bool not_write_to_file = false;
+ char *result;
+ char *value = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ result = pstrdup(value);
+ guc_free(value);
+ return result; /* yes it's awful, so the shortest way
+ * that I found */
+ }
+ else
+ val = "nil";
+ }
+ break;
default:
/* just to keep compiler quiet */
val = "???";
@@ -5658,6 +6558,21 @@ write_one_nondefault_variable(FILE *fp, struct config_generic *gconf)
config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+
+ if (conf->variable)
+ {
+ bool not_write_to_file = false;
+ char *serialized = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ fprintf(fp, "%s", serialized);
+ guc_free(serialized);
+ }
+ }
+ break;
}
fputc(0, fp);
@@ -5940,6 +6855,16 @@ estimate_variable_size(struct config_generic *gconf)
valsize = strlen(config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *) gconf;
+
+ if (conf->variable)
+ valsize = get_length_composite_str(conf->variable, conf->type_name);
+ else
+ valsize = 0;
+ }
}
/* Allow space for terminating zero-byte for value */
@@ -6098,6 +7023,16 @@ serialize_variable(char **destptr, Size *maxbytes,
config_enum_lookup_by_value(conf, *conf->variable));
}
break;
+
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ struct config_composite *conf = (struct config_composite *) gconf;
+ char *struct_str = composite_to_str(conf->variable, conf->type_name, not_write_to_file);
+
+ do_serialize(destptr, maxbytes, "%s", struct_str);
+ guc_free(struct_str);
+ }
}
do_serialize(destptr, maxbytes, "%s",
@@ -6315,6 +7250,17 @@ RestoreGUCState(void *gucstate)
guc_free(conf->reset_extra);
break;
}
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *conf = (struct config_composite *)gconf;
+
+ free_composite(conf->variable, conf->type_name);
+ if (conf->reset_val && conf->reset_val != conf->variable)
+ free_composite(conf->reset_val, conf->type_name);
+ if (conf->reset_extra && conf->reset_extra != gconf->extra)
+ guc_free(conf->reset_extra);
+ break;
+ }
}
/* Remove it from any lists it's in. */
RemoveGUCFromLists(gconf);
@@ -7008,3 +7954,53 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+static bool
+call_composite_check_hook(struct config_composite *conf, void *newval, void **extra,
+ GucSource source, int elevel)
+{
+ volatile bool result = true;
+
+ /* Quick success if no hook */
+ if (!conf->check_hook)
+ return true;
+
+ /*
+ * If elevel is ERROR, or if the check_hook itself throws an elog
+ * (undesirable, but not always avoidable), make sure we don't leak the
+ * already-malloc'd newval struct.
+ */
+ PG_TRY();
+ {
+ /* Reset variables that might be set by hook */
+ GUC_check_errcode_value = ERRCODE_INVALID_PARAMETER_VALUE;
+ GUC_check_errmsg_string = NULL;
+ GUC_check_errdetail_string = NULL;
+ GUC_check_errhint_string = NULL;
+
+ if (!conf->check_hook(newval, extra, source))
+ {
+ ereport(elevel,
+ (errcode(GUC_check_errcode_value),
+ GUC_check_errmsg_string ?
+ errmsg_internal("%s", GUC_check_errmsg_string) :
+ errmsg("invalid value for parameter \"%s\": %s",
+ conf->gen.name, newval ? composite_to_str(newval, conf->type_name, false) : ""),
+ GUC_check_errdetail_string ?
+ errdetail_internal("%s", GUC_check_errdetail_string) : 0,
+ GUC_check_errhint_string ?
+ errhint("%s", GUC_check_errhint_string) : 0));
+ /* Flush any strings created in ErrorContext */
+ FlushErrorState();
+ result = false;
+ }
+ }
+ PG_CATCH();
+ {
+ guc_free(newval);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return result;
+}
diff --git a/src/backend/utils/misc/guc_composite.c b/src/backend/utils/misc/guc_composite.c
new file mode 100644
index 0000000000..84a87f8389
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite.c
@@ -0,0 +1,1529 @@
+/*--------------------------------------------------------------------
+ * guc_composite.c
+ *
+ * This file contains the implementation of functions
+ * related to the custom composite type system.
+ *
+ * The functions are divided into 3 groups:
+ * 1. registration and support for composite types
+ * 2. support for composite options
+ * 3. printing composite options
+ *
+ * See src/backend/utils/misc/README for more information.
+ *
+ * Copyright (c) 2000-2025, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite.c
+ *
+ *--------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <float.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ucontext.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "guc_composite.h"
+#include "utils/builtins.h"
+#include "lib/stringinfo.h"
+
+
+bool extended_array_view;
+
+#define STRUCT_FIELDS_DELIMETER ';'
+#define STRUCT_FIELDS_DELIMETER_STR ";"
+
+#define element_of_data_of_dynamic_array(type_name, field) (is_dynamic_array_type(type_name) &&\
+ strcmp(field,"data") != 0 &&\
+ strcmp(field, "size") != 0)
+
+/*
+ * That structure is template of dynamic array representation in C code
+ * It is used only to get sizes and offsets of it's fields
+ */
+struct DynArrTmp
+{
+ void *data;
+ int size;
+};
+
+/* The global hash table of definitions of composite types for guc options */
+HTAB *guc_types_hashtab;
+
+static int get_type_offset(const char *type_name);
+static char *get_struct_field_type(const char *type_name, const char *field);
+static char *print_empty_array(bool writing_to_file, bool extend);
+static char *array_to_str(const void *data, int size, const char *type, bool writing_to_file, bool extend);
+static char *static_array_to_str(const void *structp, const char *type, bool writing_to_file);
+static char *dynamic_array_to_str(const void *structp, const char *type, bool writing_to_file);
+static char *scalar_to_str(const void *structp, const char *type_name, bool writing_to_file);
+static char *struct_to_str(const void *structp, const char *type, bool writing_to_file);
+static void static_array_dup(void *dest_struct, const void *src_struct, const char *type_name);
+static void struct_dup(void *dest_struct, const void *src_struct, const char *type_name);
+static int array_data_cmp(const void *first, const void *second, const char *type, int size);
+static int dynamic_array_cmp(const void *first, const void *second, const char *type);
+static int struct_cmp(const void *first, const void *second, const char *type);
+static void free_static_array(void *delptr, const char *type);
+static void free_dynamic_array(void *delptr, const char *type);
+static void free_struct(void *delptr, const char *type);
+static void dynamic_array_dup(void *dest_struct, const void *src_struct, const char *type);
+static int get_dynamic_array_size(const char *type_name, const void *structp);
+
+
+int
+get_static_array_length(const char *type_name)
+{
+ char *length_str_begin = strchr(type_name, '[');
+ char *length_str_end = NULL;
+ char *parsed_end = NULL;
+ long length = 0;
+
+ if (length_str_begin == NULL)
+ return 0;
+
+ length_str_begin++;
+
+ length_str_end = strchr(length_str_begin, ']');
+
+ if (!length_str_end)
+ return 0;
+
+ length = strtol(length_str_begin, &parsed_end, 10);
+
+ if (errno == ERANGE)
+ return 0;
+
+ while (parsed_end != length_str_end)
+ {
+ if (!isblank(*parsed_end++))
+ return 0;
+ }
+
+ return length;
+}
+
+bool
+is_static_array_type(const char *type_name)
+{
+ return (bool) get_static_array_length(type_name);
+}
+
+bool
+is_dynamic_array_type(const char *type_name)
+{
+ char *size_str_begin = strchr(type_name, '[');
+
+ if (size_str_begin && *(size_str_begin + 1) == ']')
+ return true;
+
+ return false;
+}
+
+char *
+get_array_basic_type(const char *array_type)
+{
+ ptrdiff_t first_part_len;
+ ptrdiff_t second_part_len;
+ size_t type_len;
+ char *type_name;
+ const char *bracket_close;
+ const char *bracket_open = strchr(array_type, '[');
+
+ if (!bracket_open)
+ return NULL;
+
+ bracket_close = strchr(bracket_open, ']');
+
+ if (!bracket_close)
+ return NULL;
+
+ first_part_len = bracket_open - array_type;
+ second_part_len = strchr(bracket_close, '\0') - bracket_close;
+ type_len = first_part_len + second_part_len;
+
+ type_name = guc_malloc(ERROR, type_len);
+ strncpy(type_name, array_type, first_part_len);
+ strncpy(type_name + first_part_len, bracket_close + 1, second_part_len);
+
+ return type_name;
+}
+
+struct type_definition *
+get_type_definition(const char *type_name)
+{
+ struct type_definition *definition;
+ bool found = false;
+ OptionTypeHashEntry *type_hentry = NULL;
+
+ type_hentry = (OptionTypeHashEntry *) hash_search(guc_types_hashtab, &type_name, HASH_FIND, &found);
+
+ if (found)
+ {
+ definition = type_hentry->definition;
+
+ return definition;
+ }
+
+ return NULL;
+}
+
+int
+get_array_size(const char *type_name, const int length)
+{
+ int array_size;
+ int element_size;
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type || length < 0)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+ element_size = get_composite_size(basic_type);
+ guc_free(basic_type);
+
+ if (element_offset < 0 || element_size < 0)
+ return -1;
+
+ array_size = length * (element_size + (element_size % element_offset));
+
+ return array_size;
+}
+
+static int
+get_static_array_size(const char *type_name)
+{
+ int length = get_static_array_length(type_name);
+
+ return get_array_size(type_name, length);
+}
+
+static int
+get_dynamic_array_size(const char *type_name, const void *structp)
+{
+ int length = dynamic_array_size(structp);
+
+ return get_array_size(type_name, length);
+}
+
+static int
+get_struct_size(const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type_name)))
+ return struct_type->type_size;
+
+ return -1;
+}
+
+int
+get_composite_size(const char *type_name)
+{
+ if (!type_name)
+ return -1;
+
+ /*
+ * Dynamic array is a struct that has 2 fields: pointer, int (see
+ * DynArrTmp) Do not use this function for getting size of allocated data
+ * of dynamic array
+ */
+ if (is_dynamic_array_type(type_name))
+ return sizeof(struct DynArrTmp);
+
+ if (is_static_array_type(type_name))
+ return get_static_array_size(type_name);
+
+ return get_struct_size(type_name);
+}
+
+static int
+get_array_offset(const char *type_name)
+{
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+
+ if (element_offset < 0)
+ return -1;
+
+ guc_free(basic_type);
+
+ return element_offset;
+}
+
+static int
+get_struct_offset(const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return -1;
+
+ return struct_type->offset;
+}
+
+static int
+get_type_offset(const char *type_name)
+{
+ if (!type_name)
+ return -1;
+
+ /*
+ * Dynamic array in struct that is 2 fields: pointer, int Therefore offset
+ * of pointer, int and offset of the pointer are same
+ */
+ if (is_dynamic_array_type(type_name))
+ return sizeof(void *);
+
+ if (is_static_array_type(type_name))
+ return get_array_offset(type_name);
+
+ return get_struct_offset(type_name);
+}
+
+static char *
+get_struct_field_type(const char *type_name, const char *field)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return NULL;
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ if (struct_type->fields[i].name != NULL &&
+ strcmp(field, struct_type->fields[i].name) == 0)
+ return guc_strdup(ERROR, struct_type->fields[i].type);
+ }
+
+ return NULL;
+}
+
+char *
+get_field_type_name(const char *type_name, const char *field)
+{
+ if (!type_name || !field)
+ return NULL;
+
+ /*
+ * if field is "data" or "size", dynamic array is DynArrTmp else dynamic
+ * array is allocated data
+ */
+ if (is_dynamic_array_type(type_name))
+ {
+ if (strcmp(field, "size") == 0)
+ return guc_strdup(ERROR, "int");
+
+ if (strcmp(field, "data") == 0)
+ return guc_strdup(ERROR, type_name);
+
+ return get_array_basic_type(type_name);
+ }
+
+ if (is_static_array_type(type_name))
+ return get_array_basic_type(type_name);
+
+ return get_struct_field_type(type_name, field);
+}
+
+int
+get_element_offset(const char *type_name, int index)
+{
+ int element_size;
+ int element_offset;
+ char *basic_type = get_array_basic_type(type_name);
+
+ if (!basic_type || index < 0)
+ return -1;
+
+ element_offset = get_type_offset(basic_type);
+ element_size = get_composite_size(basic_type);
+ guc_free(basic_type);
+
+ if (element_offset < 0 || element_size < 0)
+ return -1;
+
+ return element_size * index;
+}
+
+static int
+get_element_offset_with_parse_index(const char *type_name, const char *field)
+{
+ char *parsed_end = NULL;
+ long field_idx = -1;
+
+ field_idx = strtol(field, &parsed_end, 10);
+
+ if (errno == ERANGE)
+ return -1;
+
+ while (*parsed_end)
+ {
+ if (!isblank(*parsed_end++))
+ return -1;
+ }
+
+ return get_element_offset(type_name, (int) field_idx);
+}
+
+static int
+get_struct_field_offset(const char *type_name, const char *field_name, int position)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type_name)) == NULL)
+ return -1;
+
+ for (int i = 0, total_offset = 0; i < struct_type->cnt_fields; ++i)
+ {
+ int increment;
+ int local_off = get_type_offset(struct_type->fields[i].type);
+
+ if (local_off < 0)
+ return -1;
+
+ if (total_offset % local_off != 0)
+ total_offset += local_off - total_offset % local_off;
+
+ if (struct_type->fields[i].name != NULL && field_name != NULL)
+ {
+ if (strcmp(struct_type->fields[i].name, field_name) == 0)
+ return total_offset;
+ }
+ else if (i == position)
+ return total_offset;
+
+ increment = get_composite_size(struct_type->fields[i].type);
+ total_offset += increment;
+ }
+
+ return -1;
+}
+
+int
+get_field_offset(const char *type_name, const char *field_name, int position)
+{
+ if (type_name == NULL || (field_name == NULL && position < 0))
+ return -1;
+
+ /*
+ * if field_name is "data" or "size", composite value is a DynArrTmp else
+ * composite value is allocated data for dynamic array (Attention! This
+ * function cannot check length of dynamic array)
+ */
+ if (is_dynamic_array_type(type_name))
+ {
+ if (field_name)
+ {
+ if (strcmp(field_name, "data") == 0)
+ return offsetof(struct DynArrTmp, data);
+ else if (strcmp(field_name, "size") == 0)
+ return offsetof(struct DynArrTmp, size);
+ else
+ return get_element_offset_with_parse_index(type_name, field_name);
+ }
+ else
+ return get_element_offset(type_name, position);
+ }
+
+ if (is_static_array_type(type_name))
+ {
+ if (field_name)
+ return get_element_offset_with_parse_index(type_name, field_name);
+ else
+ return get_element_offset(type_name, position);
+ }
+
+ return get_struct_field_offset(type_name, field_name, position);
+}
+
+void
+init_type_definition(struct type_definition *definition)
+{
+ const char *word_del = " \t\n\v";
+ int max_offset = 0;
+ int count_fields = 1;
+ struct_field *fields = NULL; /* meta about fields */
+ char *signature_saveptr;
+ char *field_def_saveptr;
+ char *signature,
+ *field_def;
+ char *field_def_token;
+ char *word_token;
+ char *signature_buffer;
+ int curr_offset = 0;
+ int i;
+
+ /* count fields in signature */
+ const char *sym = definition->signature;
+
+ if (!sym || !*sym)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("signature of \"%s\" type is empty", definition->type_name));
+
+ return;
+ }
+
+ while (*sym)
+ {
+ if (*sym == STRUCT_FIELDS_DELIMETER)
+ count_fields++;
+
+ sym++;
+ }
+
+ /* allocate structures for field definitions */
+ fields = (struct_field *) guc_malloc(ERROR, count_fields * sizeof(struct_field));
+
+ /* parse signature */
+
+ signature = guc_strdup(ERROR, definition->signature);
+ signature_buffer = signature;
+
+ /* parse sequence of structure field definitions */
+ for (i = 0;; i++, signature = NULL)
+ {
+ int parsed_word_no = 0;
+
+ field_def_token = strtok_r(signature, STRUCT_FIELDS_DELIMETER_STR, &signature_saveptr);
+
+ if (!field_def_token)
+ break;
+
+ if (i >= count_fields)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong type signature: \"%s\" in definition of type \"%s\". It has too many fields",
+ definition->signature, definition->type_name));
+
+ goto out;
+ }
+
+ fields[i].type = NULL;
+ fields[i].name = NULL;
+
+ /*
+ * Parse field definition First word is a type, second is a name of
+ * field. Name is optional, cause there might be anonymous fields.
+ * Definitions are separated with STRUCT_FIELDS_DELIMETER
+ */
+ for (field_def = field_def_token;; field_def = NULL)
+ {
+ word_token = strtok_r(field_def, word_del, &field_def_saveptr);
+
+ if (!word_token)
+ {
+ if (parsed_word_no < 1)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong field definition: \"%s\" in definition of type \"%s\"",
+ field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ break;
+ }
+
+ word_token = guc_strdup(ERROR, word_token);
+
+ /* parse field type */
+ if (parsed_word_no == 0)
+ {
+ int type_offset = get_type_offset(word_token);
+ int type_size = get_composite_size(word_token);
+
+ if (type_offset < 0 || type_size < 0)
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong type \"%s\"is used in field definition: \"%s\" in definition of type \"%s\"",
+ word_token, field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ fields[i].type = word_token;
+
+ /* structure offset = max offset of field offsets */
+ if (type_offset > max_offset)
+ max_offset = type_offset;
+
+ /* field offset in structure % field type offset = 0 */
+ if (curr_offset % type_offset != 0)
+ curr_offset += type_offset - curr_offset % type_offset;
+
+ curr_offset += type_size;
+ }
+ else if (parsed_word_no == 1) /* parse field name */
+ fields[i].name = word_token;
+ else
+ {
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong field definition: \"%s\" in definition of type \"%s\"",
+ field_def_token, definition->type_name));
+
+ goto out;
+ }
+
+ parsed_word_no++;
+ }
+ }
+
+ /* structure size % structure offset = 0 */
+ if (curr_offset % max_offset != 0)
+ curr_offset += max_offset - curr_offset % max_offset;
+
+ definition->offset = max_offset;
+ definition->type_size = curr_offset;
+ definition->cnt_fields = count_fields;
+ definition->fields = fields;
+ fields = NULL;
+ word_token = NULL;
+
+out:
+ if (fields != NULL)
+ {
+ for (int j = 0; j < i; j++)
+ {
+ guc_free(fields[j].type);
+ guc_free(fields[j].name);
+ }
+ guc_free(fields);
+ }
+ guc_free(word_token);
+ guc_free(signature_buffer);
+
+ return;
+}
+
+char *
+get_nested_field_type_name(const char *type_name, const char *field_path)
+{
+ char *path;
+ char *cur_type_name;
+ char *cur_field;
+
+ if (!type_name || !field_path)
+ return NULL;
+
+ path = guc_strdup(ERROR, field_path);
+ cur_type_name = guc_strdup(ERROR, type_name);
+ cur_field = tokenize_field_path(path);
+ cur_field = tokenize_field_path(NULL); /* skip name of structure name */
+
+ /* Follow the path of the field */
+ while (cur_field && cur_type_name)
+ {
+ char *next_type = get_field_type_name(cur_type_name, cur_field);
+
+ guc_free(cur_type_name);
+ cur_type_name = next_type;
+
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ guc_free(path);
+
+ return cur_type_name;
+}
+
+void *
+get_nested_field_ptr(const void *composite_start, const char *type_name, const char *field_path)
+{
+ char *path;
+ char *cur_type_name;
+ char *cur_field;
+ char *cur_ptr;
+
+ if (!composite_start || !field_path || !type_name)
+ return NULL;
+
+ path = guc_strdup(ERROR, field_path);
+ cur_type_name = guc_strdup(ERROR, type_name);
+
+ cur_field = tokenize_field_path(path);
+ cur_field = tokenize_field_path(NULL); /* skip name of structure */
+ cur_ptr = (char *) composite_start;
+
+ while (cur_field && cur_type_name)
+ {
+ char *next_type;
+ int local_offset;
+
+ /* go to memory of dynamic array */
+ if (element_of_data_of_dynamic_array(cur_type_name, cur_field))
+ cur_ptr = *((char **) cur_ptr);
+
+ local_offset = get_field_offset(cur_type_name, cur_field, -1);
+
+ if (local_offset < 0)
+ {
+ cur_ptr = NULL;
+ break;
+ }
+
+ cur_ptr += local_offset;
+ next_type = get_field_type_name(cur_type_name, cur_field);
+
+ guc_free(cur_type_name);
+ cur_type_name = next_type;
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ guc_free(path);
+ guc_free(cur_type_name);
+
+ return cur_ptr;
+}
+
+static char *
+print_empty_array(bool writing_to_file, bool extend)
+{
+ if (extend)
+ {
+ if (writing_to_file)
+ return guc_strdup(ERROR, "{size: 0, data: []}");
+ else
+ return guc_strdup(ERROR, "{\n\tsize: 0,\n\tdata: []\n}");
+ }
+ else
+ return guc_strdup(ERROR, "[]");
+}
+
+static char *
+array_to_str(const void *data, int size, const char *type, bool writing_to_file, bool extend)
+{
+ const char *tab_prefix = extend ? "\t\t" : "\t";
+ StringInfoData buf;
+ char *result = NULL;
+ char *element_type;
+
+ /* process empty array */
+ if (!size)
+ print_empty_array(writing_to_file, extend);
+
+ initStringInfo(&buf);
+
+ element_type = get_array_basic_type(type);
+
+ /* write prefix */
+ if (extend)
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "{size: %d, data: [", size);
+ else
+ appendStringInfo(&buf, "{\n\tsize: %d,\n\tdata: [\n", size);
+ }
+ else
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "[");
+ else
+ appendStringInfo(&buf, "[\n");
+ }
+
+ /* recursive call for each element of array */
+ for (int i = 0; i < size; i++)
+ {
+ char *element;
+ int offset = get_element_offset(type, i);
+
+ if (offset < 0)
+ goto out;
+
+ element = composite_to_str((char *) data + offset, element_type, writing_to_file);
+
+ if (!element)
+ goto out;
+
+ if (writing_to_file)
+ {
+ appendStringInfo(&buf, "%s", element);
+
+ if (i < size - 1)
+ appendStringInfo(&buf, ", ");
+ }
+ else
+ {
+ /*
+ * if not writing to file, add tab_prefix at the beginning of each
+ * line
+ */
+ char *str_saveptr;
+ char *str_begin = strtok_r(element, "\n", &str_saveptr);
+
+ appendStringInfo(&buf, "%s%s", tab_prefix, str_begin);
+
+ while ((str_begin = strtok_r(NULL, "\n", &str_saveptr)) != NULL)
+ appendStringInfo(&buf, "\n%s%s", tab_prefix, str_begin);
+
+ if (i < size - 1)
+ appendStringInfo(&buf, ",\n");
+ else
+ appendStringInfo(&buf, "\n");
+ }
+
+ guc_free(element);
+ }
+
+ /* write suffix */
+ if (extend)
+ {
+ if (writing_to_file)
+ appendStringInfo(&buf, "]}");
+ else
+ appendStringInfo(&buf, "\t]\n}");
+ }
+ else
+ appendStringInfo(&buf, "]");
+
+ result = guc_strdup(ERROR, buf.data);
+
+out:
+ pfree(buf.data);
+ guc_free(element_type);
+
+ return result;
+}
+
+static char *
+static_array_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ int array_size = get_static_array_length(type);
+
+ return array_to_str(structp, array_size, type, writing_to_file, false);
+}
+
+static char *
+dynamic_array_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ int array_size = dynamic_array_size(structp);
+
+ /* extended_array_view - global variable (GUC) */
+ return array_to_str(*(void **) structp,
+ array_size,
+ type,
+ writing_to_file,
+ extended_array_view);
+}
+
+static char *
+scalar_to_str(const void *structp, const char *type_name, bool writing_to_file)
+{
+ char *buf;
+ char *quoted;
+
+ if (strcmp(type_name, "bool") == 0)
+ {
+ int maxlen = 6;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+
+ if (*(bool *) structp)
+ snprintf(buf, maxlen, "%s", "true");
+ else
+ snprintf(buf, maxlen, "%s", "false");
+ }
+ else if (strcmp(type_name, "int") == 0)
+ {
+ int maxlen = 12;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+ snprintf(buf, maxlen, "%d", *(int *) structp);
+ }
+ else if (strcmp(type_name, "real") == 0)
+ {
+ int maxlen = DBL_MAX_10_EXP + 3;
+
+ buf = (char *) guc_malloc(ERROR, maxlen);
+ snprintf(buf, maxlen, "%lf", *(double *) structp);
+ }
+ else if (strcmp(type_name, "string") == 0)
+ {
+ if (*(char **) structp == NULL)
+ buf = guc_strdup(ERROR, "nil");
+ else
+ {
+ /* escape quotes only if writing to file */
+ if (writing_to_file)
+ {
+ char *escaped = escape_single_quotes_ascii(*(char **) structp);
+
+ buf = guc_strdup(ERROR, escaped);
+ free(escaped);
+ }
+ else
+ buf = guc_strdup(ERROR, *(char **) structp);
+ }
+ }
+ else
+ return NULL;
+
+ /*
+ * add apostrophes: If write to file, add apostrophes for each type Else
+ * add apostrophes only for strings
+ */
+ if (writing_to_file || (strcmp(type_name, "string") == 0 && strcmp(buf, "nil") != 0))
+ {
+ int maxlen = strlen(buf) + 3;
+
+ quoted = (char *) guc_malloc(ERROR, maxlen * sizeof(char));
+ snprintf(quoted, maxlen, "\'%s\'", buf);
+ guc_free(buf);
+ }
+ else
+ quoted = buf;
+
+ return quoted;
+}
+
+bool
+is_scalar_type(const char *type_name)
+{
+ if (strcmp(type_name, "bool") == 0 ||
+ strcmp(type_name, "int") == 0 ||
+ strcmp(type_name, "real") == 0 ||
+ strcmp(type_name, "string") == 0)
+ return true;
+
+ return false;
+}
+
+static char *
+struct_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ struct type_definition *definition;
+ StringInfoData buf;
+ int cnt_fields;
+ char *result = 0;
+
+ initStringInfo(&buf);
+
+ /* check built-in types */
+ if (is_scalar_type(type))
+ return scalar_to_str(structp, type, writing_to_file);
+
+ /* standard algorithm of preparing structure for writing to file */
+ definition = NULL;
+ if ((definition = get_type_definition(type)) == NULL)
+ return NULL;
+
+ cnt_fields = definition->cnt_fields;
+
+ /* print prefix */
+ appendStringInfo(&buf, "{");
+
+ if (!writing_to_file)
+ appendStringInfo(&buf, "\n");
+
+ /* recurse call for fields */
+ for (int i = 0; i < cnt_fields; i++)
+ {
+ char *field;
+ void *sptr;
+ int offset;
+
+ if (definition->fields[i].name == NULL)
+ continue;
+
+ offset = get_field_offset(definition->type_name, NULL, i);
+
+ if (offset < 0)
+ goto out;
+
+ sptr = (char *) structp + offset;
+ field = composite_to_str(sptr, definition->fields[i].type, writing_to_file);
+
+ if (!field)
+ goto out;
+
+ if (writing_to_file)
+ {
+ appendStringInfo(&buf, "%s: %s", definition->fields[i].name, field);
+
+ if (i < cnt_fields - 1)
+ appendStringInfo(&buf, ", ");
+ }
+ else
+ {
+ /* if not write ro file, add tabs at the beginning of each line */
+ char *str_saveptr;
+ char *str_begin = strtok_r(field, "\n", &str_saveptr);
+
+ appendStringInfo(&buf, "\t%s: %s", definition->fields[i].name, str_begin);
+
+ while ((str_begin = strtok_r(NULL, "\n", &str_saveptr)) != NULL)
+ appendStringInfo(&buf, "\n\t%s", str_begin);
+
+ if (i < cnt_fields - 1)
+ appendStringInfo(&buf, ",\n");
+ else
+ appendStringInfo(&buf, "\n");
+ }
+
+ guc_free(field);
+ }
+
+ /* print suffix */
+ appendStringInfo(&buf, "}");
+ result = guc_strdup(ERROR, buf.data);
+
+out:
+ pfree(buf.data);
+
+ return result;
+}
+
+char *
+composite_to_str(const void *structp, const char *type, bool writing_to_file)
+{
+ if (is_static_array_type(type))
+ return static_array_to_str(structp, type, writing_to_file);
+
+ if (is_dynamic_array_type(type))
+ return dynamic_array_to_str(structp, type, writing_to_file);
+
+ return struct_to_str(structp, type, writing_to_file);
+}
+
+char *
+normalize_composite_value(const char *option_name, const char *value)
+{
+ /*
+ * Composite value couldn't be wrapped in quotes scalar types must be
+ * escaped and wrapped in quotes All names related to composite values
+ * ended with "->"
+ */
+ bool is_composite = suffix_is_arrow(option_name);
+ char *prepared_val;
+ char *str_val;
+
+ /*
+ * Each value that goes throw this function went throw parser before. If
+ * value is scalar, it was deescaped, else (if value is composite) it
+ * wasn't. Function parse_composite always deescapes scalar values.
+ * Therefore we must escape scalar values for parse_composite
+ */
+ if (!is_composite)
+ {
+ char *escaped = escape_single_quotes_ascii(value);
+ int len = strlen(escaped) + 3;
+
+ /* escape */
+ prepared_val = guc_malloc(ERROR, len);
+ snprintf(prepared_val, len, "\'%s\'", escaped);
+
+ free(escaped);
+ }
+ else
+ prepared_val = (char *) value; /* be careful with free */
+
+ str_val = convert_path_to_composite_value(option_name, prepared_val);
+
+ if (prepared_val != value)
+ guc_free(prepared_val);
+
+ return str_val;
+}
+
+static Size
+get_len_serialized_array(const void *structp, const char *type)
+{
+ char *element_type = get_array_basic_type(type);
+ int total_size = 3;
+ void *datap = NULL;
+ int array_size = 0;
+
+ if (is_dynamic_array_type(type))
+ {
+ array_size = dynamic_array_size(structp);
+ datap = *((void **) structp);
+ }
+ else
+ {
+ array_size = get_static_array_length(type);
+ datap = (void *) structp;
+ }
+
+ /* compute length for first element */
+ for (int i = 0; i < array_size; i++)
+ {
+ int offset = get_element_offset(type, i);
+ int element_len = get_length_composite_str((char *) datap + offset, element_type);
+
+ total_size += element_len + 2; /* 2 = len(", ") */
+ }
+
+ guc_free(element_type);
+
+ return total_size;
+}
+
+static Size
+get_len_serialized_struct(const void *structp, const char *type)
+{
+ struct type_definition *definition = NULL;
+ int total_size = 3;
+
+ if (strcmp(type, "bool") == 0)
+ return 6;
+ else if (strcmp(type, "int") == 0)
+ {
+ if (*(int *) structp < 100)
+ return 4;
+
+ return 12;
+ }
+ else if (strcmp(type, "real") == 0)
+ return 1 + 1 + 1 + REALTYPE_PRECISION + 5;
+ else if (strcmp(type, "string") == 0)
+ {
+ if (*(char **) structp)
+ return strlen(*(char **) structp);
+
+ return 5; /* len of "\nil" */
+ }
+
+ if ((definition = get_type_definition(type)) == NULL)
+ return 0;
+
+ for (int i = 0; i < definition->cnt_fields; i++)
+ {
+ int offset;
+ int field_len;
+
+ if (definition->fields[i].name == NULL)
+ continue;
+
+ offset = get_field_offset(definition->type_name, NULL, i);
+ field_len = get_length_composite_str((char *) structp + offset, definition->fields[i].type);
+
+ total_size += field_len + 2;
+ }
+
+ return total_size;
+}
+
+Size
+get_length_composite_str(const void *structp, const char *type_name)
+{
+ if (is_static_array_type(type_name) || is_dynamic_array_type(type_name))
+ return get_len_serialized_array(structp, type_name);
+
+ return get_len_serialized_struct(structp, type_name);
+}
+
+char *
+convert_path_to_composite_value(const char *field_path, const char *value)
+{
+ char *path = guc_strdup(ERROR, field_path);
+ char *cur_field = tokenize_field_path(path);
+ char *prefix = guc_strdup(ERROR, "");
+ char *suffix = guc_strdup(ERROR, "");
+ char *result;
+ int len;
+
+ /* skip guc name */
+ cur_field = tokenize_field_path(NULL);
+
+ /* for each step in path generate derived braces and name of field */
+ while (cur_field)
+ {
+ int prefix_len = strlen(prefix);
+ int suffix_len = strlen(suffix);
+
+ int prefix_diff_len = 3 + strlen(cur_field) + 1; /* 3 for "[: ", 1 for
+ * '\0' */
+ int suffix_diff_len = 2;
+
+ char *next_prefix = guc_malloc(ERROR, prefix_len + prefix_diff_len);
+ char *next_suffix = guc_malloc(ERROR, suffix_len + suffix_diff_len);
+
+ snprintf(next_prefix, prefix_len + 1, "%s", prefix);
+ /* define array or structure */
+ if (isdigit(cur_field[0]))
+ {
+ snprintf(next_prefix + prefix_len, 2, "[");
+ snprintf(next_suffix, 2, "]");
+ }
+ else
+ {
+ snprintf(next_prefix + prefix_len, 2, "{");
+ snprintf(next_suffix, 2, "}");
+ }
+
+ snprintf(next_prefix + prefix_len + 1, prefix_diff_len - 1, "%s: ", cur_field);
+ snprintf(next_suffix + 1, suffix_len + 1, "%s", suffix);
+
+ guc_free(prefix);
+ guc_free(suffix);
+
+ prefix = next_prefix;
+ suffix = next_suffix;
+
+ cur_field = tokenize_field_path(NULL);
+ }
+
+ /* construct result from prefix, suffix and value */
+ len = strlen(prefix) + strlen(value) + strlen(suffix) + 1;
+ result = guc_malloc(ERROR, len);
+ snprintf(result, len, "%s%s%s", prefix, value, suffix);
+
+ guc_free(prefix);
+ guc_free(suffix);
+
+ return result;
+}
+
+static void
+static_array_dup(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ const char *basic_type = get_array_basic_type(type_name);
+ int arr_size = get_static_array_length(type_name);
+
+ /* recursive duplicate array elements */
+ for (int i = 0; i < arr_size; i++)
+ {
+ int offset = get_element_offset(type_name, i);
+ void *dest_ptr = (char *) dest_struct + offset;
+ void *src_ptr = (char *) src_struct + offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, basic_type);
+ }
+}
+
+/*
+ * Beware! src_struct points to structure like DynArrTmp
+ */
+static void
+dynamic_array_dup(void *dest_struct, const void *src_struct, const char *type)
+{
+ void *datap;
+ void **dstpp;
+ void *dstp;
+ const char *basic_type = get_array_basic_type(type);
+ int arr_mem_size = get_dynamic_array_size(type, src_struct);
+ int arr_size = dynamic_array_size(src_struct);
+
+ if (!arr_size)
+ {
+ *(void **) dest_struct = NULL;
+ *((void **) dest_struct + 1) = NULL;
+
+ return;
+ }
+
+ datap = *((void **) src_struct);
+ dstpp = (void **) dest_struct;
+ *dstpp = guc_malloc(ERROR, arr_mem_size * sizeof(char));
+ dstp = *dstpp;
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ int offset = get_element_offset(type, i);
+ void *dest_ptr = (char *) dstp + offset;
+ void *src_ptr = (char *) datap + offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, basic_type);
+ }
+
+ dynamic_array_size(dest_struct) = arr_size;
+}
+
+static void
+struct_dup(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return;
+
+ if (is_scalar_type(type_name))
+ {
+ if (!strcmp(type_name, "string"))
+ {
+ if (*(char **) src_struct)
+ *(char **) dest_struct = guc_strdup(ERROR, *(char **) src_struct);
+ else
+ *(char **) dest_struct = NULL;
+
+ return;
+ }
+
+ memcpy(dest_struct, src_struct, struct_type->type_size);
+
+ return;
+ }
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char * field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type_name, NULL, i);
+ void * dest_ptr = (char *) dest_struct + field_offset;
+ void * src_ptr = (char *) src_struct + field_offset;
+
+ composite_dup_impl(dest_ptr, src_ptr, field_type);
+ }
+}
+
+void
+composite_dup_impl(void *dest_struct, const void *src_struct, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ return static_array_dup(dest_struct, src_struct, type_name);
+ if (is_dynamic_array_type(type_name))
+ return dynamic_array_dup(dest_struct, src_struct, type_name);
+
+ return struct_dup(dest_struct, src_struct, type_name);
+}
+
+void *
+composite_dup(const void *structp, const char *type_name)
+{
+ int struct_size;
+ void *duplicate;
+
+ if (!structp)
+ return NULL;
+
+ struct_size = get_composite_size(type_name);
+ duplicate = guc_malloc(ERROR, struct_size);
+
+ /* recursive bypass and searching string */
+ composite_dup_impl(duplicate, structp, type_name);
+
+ return duplicate;
+}
+
+static int
+array_data_cmp(const void *first, const void *second, const char *type, int size)
+{
+ const char *base_type = get_array_basic_type(type);
+ int base_type_size = get_composite_size(base_type);
+ int res = 0;
+
+ /* recursive compare each element */
+ for (int i = 0; i < size; i++)
+ {
+ int offset = base_type_size * i;
+ void *first_element = (char *) first + offset;
+ void *second_element = (char *) second + offset;
+
+ res = composite_cmp(first_element, second_element, base_type);
+
+ if (res)
+ break;
+ }
+
+ return res;
+}
+
+static int
+dynamic_array_cmp(const void *first, const void *second, const char *type)
+{
+ void *first_data = *((void **) first);
+ void *second_data = *((void **) second);
+
+ int first_size = dynamic_array_size(first);
+ int second_size = dynamic_array_size(second);
+
+ int cmp = 0;
+
+ if ((cmp = first_size - second_size))
+ return cmp;
+
+ return array_data_cmp(first_data, second_data, type, first_size);
+}
+
+static int
+struct_cmp(const void *first, const void *second, const char *type_name)
+{
+ int res;
+
+ /* check type */
+ struct type_definition *struct_type = NULL;
+
+ if (!(struct_type = get_type_definition(type_name)))
+ return 2; /* error code */
+
+ /* check scalar types like int, real, etc */
+ if (struct_type->cnt_fields == 0)
+ {
+ /* compare string with strcmp, not pointers! */
+ res = 0;
+
+ if (strcmp(type_name, "string") == 0)
+ {
+ if (!*(char **) first && !*(char **) second)
+ return 0;
+
+ if (!*(char **) first)
+ return -1;
+
+ if (!*(char **) second)
+ return 1;
+
+ res = strcmp(*(char **) first, *(char **) second);
+ }
+ else if (strcmp(type_name, "bool") == 0)
+ res = *(bool *) first - *(bool *) second;
+ else if (strcmp(type_name, "int") == 0)
+ res = *(int *) first - *(int *) second;
+ else if (strcmp(type_name, "real") == 0)
+ {
+ double res = *(double *) first - *(double *) second;
+
+ if (res == 0)
+ return 0;
+
+ if (res > 0)
+ return 1;
+
+ return -1;
+ }
+ else
+ return 2;
+
+ if (res == 0)
+ return 0;
+
+ if (res > 0)
+ return 1;
+
+ return -1;
+ }
+
+ /* recursive comparison of fields */
+ res = 0;
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char * field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type_name, NULL, i);
+ void * first_field = (char *) first + field_offset;
+ void * second_field = (char *) second + field_offset;
+
+ res = composite_cmp(first_field, second_field, field_type);
+
+ if (res)
+ break;
+ }
+
+ return res;
+}
+
+int
+composite_cmp(const void *first, const void *second, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ return array_data_cmp(first, second, type_name, get_static_array_length(type_name));
+
+ if (is_dynamic_array_type(type_name))
+ return dynamic_array_cmp(first, second, type_name);
+
+ return struct_cmp(first, second, type_name);
+}
+
+static void
+free_static_array(void *delptr, const char *type)
+{
+ const char *base_type = get_array_basic_type(type);
+ int arr_size = get_static_array_length(type);
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ void *element_ptr = (char *) delptr + get_element_offset(type, i);
+
+ free_composite_impl(element_ptr, base_type);
+ }
+}
+
+static void
+free_dynamic_array(void *delptr, const char *type)
+{
+ const char *base_type = get_array_basic_type(type);
+ int arr_size = get_static_array_length(type);
+ void **datapp = NULL;
+
+ for (int i = 0; i < arr_size; i++)
+ {
+ void *element_ptr = (char *) delptr + get_element_offset(type, i);
+
+ free_composite_impl(element_ptr, base_type);
+ }
+
+ datapp = (void **) delptr;
+ guc_free(*datapp);
+ *datapp = NULL;
+}
+
+static void
+free_struct(void *delptr, const char *type)
+{
+ struct type_definition *struct_type = NULL;
+
+ if ((struct_type = get_type_definition(type)) == NULL)
+ return;
+
+ if (struct_type->cnt_fields == 0)
+ {
+ if (strcmp(type, "string") == 0)
+ {
+ char **strp = (char **) delptr;
+
+ guc_free(*strp);
+ *strp = NULL;
+ }
+
+ return;
+ }
+
+ for (int i = 0; i < struct_type->cnt_fields; i++)
+ {
+ const char * field_type = struct_type->fields[i].type;
+ int field_offset = get_field_offset(type, NULL, i);
+
+ free_composite_impl((char *) delptr + field_offset, field_type);
+ }
+}
+
+void
+free_composite_impl(void *delptr, const char *type_name)
+{
+ if (is_static_array_type(type_name))
+ free_static_array(delptr, type_name);
+
+ if (is_dynamic_array_type(type_name))
+ free_dynamic_array(delptr, type_name);
+
+ free_struct(delptr, type_name);
+}
+
+void
+free_composite(void *delptr, const char *type_name)
+{
+ free_composite_impl(delptr, type_name);
+ guc_free(delptr);
+}
diff --git a/src/backend/utils/misc/guc_composite.h b/src/backend/utils/misc/guc_composite.h
new file mode 100644
index 0000000000..a7a8dc4ac1
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite.h
@@ -0,0 +1,84 @@
+/*--------------------------------------------------------------------
+ * guc_composite.h
+ *
+ * Declarations shared between backend/utils/misc/guc.c and
+ * backend/utils/misc/guc_composite.c
+ *
+ * Copyright (c) 2000-2025, PostgreSQL Global Development Group
+ *
+ * src/backend/utils/misc/guc_composite.h
+ *--------------------------------------------------------------------
+ */
+#ifndef GUC_COMPOSITE_H
+#define GUC_COMPOSITE_H
+
+#include "utils/guc.h"
+#include "utils/guc_tables.h"
+#include "utils/hsearch.h"
+
+typedef struct
+{
+ const char *type_name;
+ struct type_definition *definition;
+} OptionTypeHashEntry;
+
+#define IS_STATUS_OK(val) (val.status == PARSER_OK)
+#define IS_STATUS_FAIL(val) (val.status == PARSER_FAIL)
+#define IS_STATUS_ERR(val) (val.status == PARSER_ERR)
+#define IS_STATUS_NOT_FOUND(val) (val.status == PARSER_NOT_FOUND)
+
+/*
+ * Get size in dynamic array. It places after pointer to data
+ */
+#define dynamic_array_size(ptr) (*(int *)((char *)ptr + sizeof(void *)))
+
+#define suffix_is_arrow(name) (name[strlen(name) - 2] == '-' && name[strlen(name) - 1] == '>')
+
+/*
+ * Tokenized path to nest structures. It replaces '->' to '\0' and
+ * returns pointer to first member name.
+ */
+#define tokenize_field_path(path) strtok(path, "->[]")
+
+extern HTAB *guc_types_hashtab;
+
+extern Size get_length_composite_str(const void *structp, const char *type_name);
+extern void init_type_definition(struct type_definition *definition);
+extern struct type_definition *get_type_definition(const char *type_name);
+extern bool is_static_array_type(const char *type_name);
+extern bool is_dynamic_array_type(const char *type_name);
+extern int get_static_array_length(const char *type_name);
+extern int get_array_size(const char *type_name, const int length);
+extern int get_composite_size(const char *type_name);
+extern void composite_dup_impl(void *dest_struct, const void *src_struct, const char *type_name);
+extern void *composite_dup(const void *structp, const char *type_name);
+extern int composite_cmp(const void *first, const void *second, const char *type_name);
+extern char *get_field_type_name(const char *type_name, const char *field);
+extern char *get_nested_field_type_name(const char *type_name, const char *field_path);
+extern int get_field_offset(const char *type_name, const char *field_name, int position);
+extern int get_element_offset(const char *type_name, int index);
+extern void free_composite_impl(void *delptr, const char *type_name);
+extern void free_composite(void *delptr, const char *type_name);
+extern void *get_nested_field_ptr(const void *composite_start, const char *type_name, const char *field_path);
+extern char *normalize_composite_value(const char *option_name, const char *value);
+extern bool parse_composite(const char *strvalue, const char *type, void **result, const void *prev_val, int flags, const char **hintmsg);
+extern char *convert_path_to_composite_value(const char *field_path, const char *value);
+extern char *get_array_basic_type(const char *array_type_name);
+extern bool is_scalar_type(const char *type_name);
+
+/*
+ * Internal functions for parsing guc_composite grammar,
+ * in guc_composite_gram.y and guc_composite_scan.l
+ */
+union YYSTYPE;
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void *yyscan_t;
+#endif
+extern int guc_composite_yyparse(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner);
+extern void guc_composite_yyerror(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner, const char *message);
+extern int guc_composite_yylex(union YYSTYPE *yylval_param, const char **hintmsg, yyscan_t yyscanner);
+extern void guc_composite_scanner_init(const char *str, yyscan_t *yyscannerp);
+extern void guc_composite_scanner_finish(yyscan_t yyscanner);
+
+#endif /* GUC_COMPOSITE_H */
diff --git a/src/backend/utils/misc/guc_composite_gram.y b/src/backend/utils/misc/guc_composite_gram.y
new file mode 100644
index 0000000000..7b09182199
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite_gram.y
@@ -0,0 +1,787 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * guc_composite_gram.y - Parser for all composite guc options
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "nodes/pg_list.h"
+#include "utils/guc.h"
+#include "utils/builtins.h"
+#include "guc_composite.h"
+#include "guc_composite_gram.h"
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Bison doesn't allocate anything that needs to live across parser calls,
+ * so we can easily have it use palloc instead of malloc. This prevents
+ * memory leaks if we error out during parsing.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+extern int guc_composite_yychar;
+extern int guc_composite_yynerrs;
+
+#define context(num) ((parser_ctx *)list_nth(contexts, num))
+#define list_empty(l) (list_length(l) == 0)
+#define last_context (*(parser_ctx **)list_tail(contexts))
+
+#define check_error() do { \
+ if(*hintmsg) \
+ return 1; \
+ } while(0)
+
+/* Stack is needed to memoize valuable data between nested layers in composite object */
+enum name_usage
+{
+ NAME_USAGE_UNKNOWN,
+ NAME_USAGE_ALWAYS,
+ NAME_USAGE_NEVER
+};
+
+/* Stages of setting size for dynamic arrays */
+enum fixed_size
+{
+ FIXED_SIZE_IS_NOT_SETTED, /* Size was not defined for dynamic array */
+ FIXED_SIZE_IS_BEING_SETTED, /* Current value must be setted as a length of
+ * dynamic array */
+ FIXED_SIZE_IS_SETTED /* Indexes in array must be less than size */
+};
+
+typedef struct parser_ctx
+{
+ char *type; /* type of composite object */
+ void *start; /* pointer to start of composite object */
+ int idx; /* automatic computed index for current child */
+ bool has_name; /* name (index) was parsed before current
+ * composite object */
+ bool extended; /* flag for dynamic arrays. True = extended
+ * text representation */
+ enum fixed_size fixed_size; /* field for outer context of dynamic array.
+ * see comments for enum */
+ int max_idx; /* field for outer context of dynamic array.
+ * sets value of max idx in data */
+ enum name_usage name_usage; /* names (indexes) are used for children of
+ * composite object */
+} parser_ctx;
+
+static List *contexts = NIL; /* stack of contexts */
+
+static void init_context(const char *type, void *ptr);
+static void push_context(const char *type, void *start);
+static void free_context(parser_ctx * context);
+static void free_context_list(void);
+static void check_name(yyscan_t yyscanner, const char **hintmsg);
+static void reallocate_dynamic_array(const char *array_type, void *array, int new_len);
+static void check_memory(yyscan_t yyscanner, const char **hintmsg);
+static void prepare_value_context(yyscan_t yyscanner, const char **hintmsg);
+static void prepare_structure(void);
+static void prepare_array(void);
+static void parse_composite_end(void);
+static void parse_field_end(void);
+static int parse_index(const char *index, yyscan_t yyscanner, const char **hintmsg);
+static void parse_element(const char *index, yyscan_t yyscanner, const char **hintmsg);
+static void parse_field(const char *name, yyscan_t yyscanner, const char **hintmsg);
+static void parse_name(const char *name, yyscan_t yyscanner, const char **hintmsg);
+static void parse_scalar_opt(char *strval, const char *struct_type, void *result, int flags, yyscan_t yyscanner, const char **hintmsg);
+%}
+
+%parse-param {void *composite_ptr}
+%parse-param {const char *composite_type}
+%parse-param {const char **hintmsg}
+%parse-param {int flags}
+%parse-param {yyscan_t yyscanner}
+%lex-param {const char **hintmsg}
+%lex-param {yyscan_t yyscanner}
+%pure-parser
+%expect 0
+%name-prefix="guc_composite_yy"
+
+%union
+{
+ char *str;
+}
+
+%token <str> IDENT JUNK
+
+%type placeholder_patch_list
+%type composite
+%type list_or_empty
+%type list
+%type item
+
+%start placeholder_patch_list
+
+%initial-action
+{
+ init_context(composite_type, composite_ptr);
+ (void) yynerrs;
+}
+%%
+
+placeholder_patch_list:
+ composite ';' {
+ init_context(composite_type, composite_ptr);
+ check_error();
+ }
+ placeholder_patch_list
+ | composite
+
+composite:
+ '{' {
+ prepare_value_context(yyscanner, hintmsg);
+ prepare_structure();
+ check_error();
+ }
+ list_or_empty {
+ parse_composite_end();
+ check_error();
+ }
+ '}'
+ | '[' {
+ prepare_value_context(yyscanner, hintmsg);
+ check_error();
+ prepare_array();
+ check_error();
+ }
+ list_or_empty {
+ parse_composite_end();
+ check_error();
+ }
+ ']'
+ | IDENT {
+ prepare_value_context(yyscanner, hintmsg);
+ check_error();
+ parse_scalar_opt($1, context(0)->type, context(0)->start, flags, yyscanner, hintmsg);
+ check_error();
+ parse_composite_end();
+ check_error();
+ }
+ ;
+
+list_or_empty:
+ list
+ | %empty
+ ;
+
+list:
+ item {
+ parse_field_end();
+ check_error();
+ }
+ ',' list
+ | item
+ ;
+
+item:
+ IDENT ':' {
+ parse_name($1, yyscanner, hintmsg);
+ check_error();
+ }
+ composite
+ | composite
+ ;
+%%
+
+
+static void
+init_context(const char *type, void *ptr)
+{
+ free_context_list();
+ push_context(type, ptr);
+}
+
+static void
+push_context(const char *type, void *start)
+{
+ parser_ctx *ctx = palloc(sizeof(parser_ctx));
+
+ if (type)
+ ctx->type = pstrdup(type);
+ else
+ ctx->type = NULL;
+
+ ctx->start = start;
+ ctx->idx = 0;
+ ctx->has_name = false;
+ ctx->extended = false;
+ ctx->fixed_size = FIXED_SIZE_IS_NOT_SETTED;
+ ctx->max_idx = -1;
+ ctx->name_usage = NAME_USAGE_UNKNOWN;
+
+ contexts = lcons(ctx, contexts);
+}
+
+static void
+free_context(parser_ctx * context)
+{
+ if (context->type)
+ pfree(context->type);
+
+ if (context)
+ pfree(context);
+}
+
+static void
+free_context_list(void)
+{
+ while (!list_empty(contexts))
+ {
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+ }
+
+ list_free(contexts);
+}
+
+static void
+check_name(yyscan_t yyscanner, const char **hintmsg)
+{
+ /* Indexes in array are exist either for each element or for no one */
+ if (is_static_array_type(context(1)->type) ||
+ (is_dynamic_array_type(context(1)->type) && context(1)->extended != true))
+ {
+ if (context(0)->has_name)
+ {
+ if (context(1)->name_usage == NAME_USAGE_NEVER)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_ALWAYS;
+ }
+ else
+ {
+ if (context(1)->name_usage == NAME_USAGE_ALWAYS)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_NEVER;
+
+ if (context(1)->start)
+ context(0)->start = (char *) context(1)->start + get_element_offset(context(1)->type, context(1)->idx);
+ }
+ }
+ else if (!context(0)->has_name) /* fields of structures must be labeled
+ * always */
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "all fields in structure must be labeled");
+ return;
+ }
+}
+
+static void
+reallocate_dynamic_array(const char *array_type, void *array, int new_len)
+{
+ int new_arr_mem_size = get_array_size(array_type, new_len);
+ int last_len = dynamic_array_size(array);
+ int last_arr_mem_size = get_array_size(array_type, last_len);
+ int min_arr_mem_size = new_arr_mem_size < last_arr_mem_size ? new_arr_mem_size : last_arr_mem_size;
+ void *new_data = guc_malloc(ERROR, new_arr_mem_size);
+
+ if (new_len < last_len)
+ {
+ /* free elements from deleted part */
+ char *basic_type = get_array_basic_type(array_type);
+
+ for (int i = new_len; i < last_len; i++)
+ {
+ int offset = get_element_offset(array_type, i);
+
+ free_composite_impl((char *) (*(void **) array) + offset, basic_type);
+ }
+
+ guc_free(basic_type);
+ }
+ else
+ memset((char *) new_data + last_arr_mem_size, 0, new_arr_mem_size - last_arr_mem_size);
+
+ memcpy(new_data, *(void **) array, min_arr_mem_size);
+ guc_free(*(void **) array);
+
+ *(void **) array = new_data;
+ dynamic_array_size(array) = new_len;
+}
+
+static void
+check_memory(yyscan_t yyscanner, const char **hintmsg)
+{
+ int len = 0;
+ int idx = 0;
+
+ /*
+ * next part of function process a case, when we expect parsing elements
+ * of data of dynamic array therefore context(1) must be not extended
+ * (i.e. inner context)
+ *
+ * If context(1) is extended dynamic array (i.e. outer context), we are
+ * going to parse "data" or "size" field
+ */
+ if (!(is_dynamic_array_type(context(1)->type)
+ && context(1)->extended != true))
+ return;
+
+ len = dynamic_array_size(context(2)->start);
+ idx = context(1)->idx;
+
+ if (idx > context(2)->max_idx)
+ context(2)->max_idx = idx;
+
+ if (idx >= len)
+ {
+ int offset;
+
+ if (context(2)->fixed_size == FIXED_SIZE_IS_SETTED)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "there is index greater than array length.\
+ Change \"size\" field");
+ return;
+ }
+
+ reallocate_dynamic_array(context(2)->type, context(2)->start, idx + 1);
+
+ /* update contexts about dynamic array */
+ context(1)->start = *(void **) context(2)->start;
+ offset = get_element_offset(context(1)->type, context(1)->idx);
+ context(0)->start = (char *) context(1)->start + offset;
+ }
+}
+
+/*
+ * Check all conditions related to context: name, allocated memory
+ */
+static void
+prepare_value_context(yyscan_t yyscanner, const char **hintmsg)
+{
+ /* top-level structure always is okey */
+ if (list_length(contexts) == 1)
+ return;
+ check_name(yyscanner, hintmsg);
+ check_memory(yyscanner, hintmsg);
+}
+
+/*
+ * Prepare context for structure. Add new layer in stack that will be filled by parse_name
+ */
+static void
+prepare_structure(void)
+{
+ /* if type is dynamic array => that is extended form of array */
+ if (is_dynamic_array_type(context(0)->type))
+ context(0)->extended = true;
+
+ /* add context for structure field */
+ push_context(NULL, context(0)->start);
+}
+
+/*
+ * Prepare context for array. Add new layer to stack that will be filled by parse_name.
+ * In case of parsing dynamic array we must guarantee that
+ * current context->start points to pointer to data of dynamic array
+ */
+static void
+prepare_array(void)
+{
+ void *data = NULL;
+ char *basic_type = get_array_basic_type(context(0)->type);
+
+ if (!is_dynamic_array_type(context(0)->type))
+ {
+ push_context(pstrdup(basic_type), context(0)->start);
+
+ goto out;
+ }
+ else
+ {
+ /*
+ * There are 4 cases that could be: "{data: [" or "[" or "[ [" or "{
+ * field: [" and only first case don't need creating new stack layer
+ */
+ if (!(list_length(contexts) > 1 && context(1)->extended))
+ {
+ data = *(void **) context(0)->start;
+
+ /*
+ * Each dynamic array has 2 contexts: outer context for structure
+ * {data, size} And inner context for allocated data. Because of
+ * different ways to set dynamic array (extended and compact
+ * forms) outer context of dynamic array has flag "extended". When
+ * we pop from stack, we see this flag and for compact
+ * representation of array we pop twice (for purpose of popping
+ * inner and outer contexts at one time)
+ */
+ if (data)
+ push_context(context(0)->type, data);
+ else
+ push_context(context(0)->type, NULL);
+ }
+
+ data = context(0)->start;
+
+ /*
+ * Current dynamic array could be empty. In this case create
+ * fictitious stack layer (NULL in start field means that now we parse
+ * element of empty dynamic array) for purposes of recursive algorithm
+ */
+ if (data)
+ push_context(pstrdup(basic_type), data);
+ else
+ push_context(pstrdup(basic_type), NULL);
+ }
+
+out:
+ guc_free(basic_type);
+}
+
+/*
+ * Update context when parser go out of nested
+ * level of structure. So, rewind stack of contexts.
+ */
+static void
+parse_composite_end(void)
+{
+ /*
+ * is_dynamic is used to not miss in case: Delete context and view not
+ * extended dynamic array. So that is inner or outer context? If
+ * is_dynamic == true => that is outer context Else that is inner context
+ */
+ bool is_dynamic = false;
+
+ /*
+ * is_extended is used in cases when we delete outer context of array that
+ * is nested in dynamic array in compact form
+ */
+ bool is_extended = context(0)->extended;
+
+ /*
+ * type of current context might have NULL type in case than we parse {}
+ * Then go exactly to deleting context
+ */
+ if (context(0)->type)
+ is_dynamic = is_dynamic_array_type(context(0)->type);
+
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+
+ /*
+ * If deleted layer was extended, that layer was outer context => return
+ */
+ if (is_extended)
+ return;
+
+ /*
+ * Free up outer context for dynamic array in compact form. See comments
+ * in parse_array function
+ */
+ if (!list_empty(contexts)
+ && is_dynamic
+ && is_dynamic_array_type(context(0)->type)
+ && context(0)->extended != true)
+ {
+ free_context((parser_ctx *) list_nth(contexts, 0));
+ contexts = list_delete_first(contexts);
+ }
+}
+
+/*
+ * Update context before parser go to parse next field
+ */
+static void
+parse_field_end(void)
+{
+ context(0)->idx++; /* context of structure/array */
+
+ if (is_dynamic_array_type(context(0)->type) || is_static_array_type(context(0)->type))
+ {
+ void *data = *(void **) context(0)->start;
+ char *basic_type = get_array_basic_type(context(0)->type);
+
+ if (data)
+ push_context(pstrdup(basic_type), data);
+ else
+ push_context(pstrdup(basic_type), NULL);
+
+ guc_free(basic_type);
+ }
+ else
+ push_context(NULL, context(0)->start);
+
+ context(0)->has_name = false; /* context of field/element */
+}
+
+static int
+parse_index(const char *index, yyscan_t yyscanner, const char **hintmsg)
+{
+ for (const char *c = index; *c; c++)
+ {
+ if (!isdigit(*c))
+ {
+ guc_composite_yyerror(last_context->start, last_context->type, hintmsg, 0, yyscanner, "incorrect index");
+ return -1;
+ }
+ }
+
+ return atoi(index);
+}
+
+/*
+ * Parse index and compute offset in array. Fill current context
+ */
+static void
+parse_element(const char *index, yyscan_t yyscanner, const char **hintmsg)
+{
+ int idx = parse_index(index, yyscanner, hintmsg);
+
+ if (*hintmsg)
+ return;
+
+ /* for static array check len */
+ if (is_static_array_type(context(1)->type))
+ {
+ int len = get_static_array_length(context(1)->type);
+
+ if (idx >= len)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "there is index which is greater than size of array");
+ return;
+ }
+ }
+
+ context(1)->idx = idx;
+ context(0)->start = (char *) context(1)->start + get_element_offset(context(1)->type, idx);
+}
+
+/*
+ * Parse name, check correctness. Fill current context
+ */
+static void
+parse_field(const char *name, yyscan_t yyscanner, const char **hintmsg)
+{
+ char *field_type;
+ int offset = get_field_offset(context(1)->type, name, -1);
+
+ if (offset < 0)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "incorrect field name");
+ return;
+ }
+
+ /* create new context */
+ field_type = get_field_type_name(context(1)->type, name);
+ context(0)->type = pstrdup(field_type);
+ context(0)->start = (char *) context(1)->start + offset;
+ guc_free(field_type);
+
+ /* process fields size and data in extended version of dynamic array */
+ if (context(1)->extended)
+ {
+ if (strcmp(name, "size") == 0)
+ context(1)->fixed_size = FIXED_SIZE_IS_BEING_SETTED;
+ else if (strcmp(name, "data") == 0)
+ {
+ void *data = *(void **) context(1)->start;
+
+ if (data)
+ context(0)->start = data;
+ else
+ context(0)->start = NULL;
+ }
+ }
+}
+
+/*
+ * Compute the pointer to field by name of field, fill current context
+ */
+static void
+parse_name(const char *name, yyscan_t yyscanner, const char **hintmsg)
+{
+ /* Name could be an index for arrays */
+ if (is_static_array_type(context(1)->type) ||
+ (is_dynamic_array_type(context(1)->type) && context(1)->extended == false))
+ {
+ if (context(1)->name_usage == NAME_USAGE_NEVER)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "use indexes for either everyone or no one");
+ return;
+ }
+
+ context(1)->name_usage = NAME_USAGE_ALWAYS;
+ parse_element(name, yyscanner, hintmsg);
+ }
+ else
+ parse_field(name, yyscanner, hintmsg);
+
+ context(0)->has_name = true;
+}
+
+static void
+parse_scalar_opt(char *strval, const char *struct_type, void *result, int flags, yyscan_t yyscanner, const char **hintmsg)
+{
+ if (strcmp(struct_type, "bool") == 0)
+ {
+ if (!parse_bool(strval, (bool *) result))
+ {
+ guc_composite_yyerror(last_context->start, last_context->type, hintmsg, 0, yyscanner, "failed to parse bool value, use 'on' and 'off'");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "int") == 0)
+ {
+ /*
+ * Block of code for field "size" in dynamic array This block of code
+ * must be above parse_int(). Because standard parsing will overwrite
+ * old length, but we need it in reallocate_dynamic_array()
+ */
+ if (list_length(contexts) > 1 && context(1)->fixed_size == FIXED_SIZE_IS_BEING_SETTED)
+ {
+ int len = parse_index(strval, yyscanner, hintmsg);
+
+ if (*hintmsg)
+ return;
+
+ if (len <= context(1)->max_idx)
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "fixed size of dynamic array less\
+ or eaual maximum index");
+ return;
+ }
+
+ reallocate_dynamic_array(context(1)->type, context(1)->start, len);
+ context(1)->fixed_size = FIXED_SIZE_IS_SETTED;
+ }
+
+ if (!parse_int(strval, (int *) result, flags, hintmsg))
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to parse int value, check units");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "real") == 0)
+ {
+ if (!parse_real(strval, (double *) result, flags, hintmsg))
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to parse real value, check delimiter");
+ return;
+ }
+ }
+ else if (strcmp(struct_type, "string") == 0)
+ {
+ if (strcmp(strval, "\\nil") == 0)
+ *((char **) result) = NULL;
+ else
+ {
+ if (*((char **) result))
+ guc_free(*((char **) result));
+
+ *((char **) result) = guc_strdup(ERROR, strval);
+ }
+ }
+ else
+ {
+ guc_composite_yyerror(last_context->start,
+ last_context->type,
+ hintmsg,
+ 0,
+ yyscanner,
+ "failed to determine type of simple field");
+ return;
+ }
+}
+
+bool
+parse_composite(const char *strvalue, const char *type, void **result, const void *prev_val, int flags, const char **hintmsg)
+{
+ int size = 0;
+ yyscan_t scanner;
+ void *val = NULL;
+ int parser_result = 0;
+ bool check = true;
+
+ *hintmsg = NULL;
+ size = get_composite_size(type);
+ val = guc_malloc(ERROR, size);
+
+ if (prev_val)
+ composite_dup_impl(val, prev_val, type);
+ else
+ memset(val, 0, size);
+
+ guc_composite_scanner_init(strvalue, &scanner);
+ parser_result = guc_composite_yyparse(val, type, hintmsg, flags, scanner);
+ guc_composite_scanner_finish(scanner);
+ free_context_list();
+ if (parser_result != 0 || *hintmsg)
+ {
+ guc_free(val);
+ *result = NULL;
+ check = false;
+ }
+ else
+ *result = val;
+
+ return check;
+}
diff --git a/src/backend/utils/misc/guc_composite_scan.l b/src/backend/utils/misc/guc_composite_scan.l
new file mode 100644
index 0000000000..1f249ea87a
--- /dev/null
+++ b/src/backend/utils/misc/guc_composite_scan.l
@@ -0,0 +1,132 @@
+%top{
+/*-------------------------------------------------------------------------
+ *
+ * guc_composite_scan.l
+ * a lexical scanner for all composite guc values
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/misc/guc_composite_scan.l
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "utils/guc.h"
+#include "guc_composite.h"
+#include "guc_composite_gram.h"
+
+#define YY_DECL extern int guc_composite_yylex(union YYSTYPE *yylval_param, const char **hintmsg, yyscan_t yyscanner)
+}
+%option reentrant
+%option bison-bridge
+%option 8bit
+%option never-interactive
+%option nodefault
+%option noinput
+%option nounput
+%option noyywrap
+%option noyyalloc
+%option noyyrealloc
+%option noyyfree
+%option warn
+%option prefix="guc_composite_yy"
+
+SPACE [ \t\n\r\f\v]
+
+LETTER_OR_DIGIT [A-Za-z_0-9\200-\377]
+
+STRING \'([^'\\\n]|\\.|\'\')*\'
+UNQUOTED_STRING ({LETTER_OR_DIGIT}|\.)*
+
+
+%%
+{SPACE} /* Eat whitespace */
+
+{STRING} {
+ /* Deescape string and return value */
+ yylval->str = DeescapeQuotedString(yytext);
+ return IDENT;
+ }
+
+{UNQUOTED_STRING} {
+ yylval->str = pstrdup(yytext);
+ return IDENT;
+ }
+
+"{" { return '{'; }
+"}" { return '}'; }
+"[" { return '['; }
+"]" { return ']'; }
+"," { return ','; }
+":" { return ':'; }
+";" { return ';'; }
+
+. {
+ guc_composite_yyerror(NULL, NULL, hintmsg, 0, yyscanner, "illegible lexeme");
+ return JUNK;
+ }
+%%
+
+/* functions for lexer */
+
+void
+guc_composite_scanner_init(const char *str, yyscan_t *yyscannerp)
+{
+ yyscan_t yyscanner;
+
+ if (yylex_init(yyscannerp) != 0)
+ elog(ERROR, "yylex_init() failed: %m");
+ yyscanner = *yyscannerp;
+ yy_scan_string(str, yyscanner);
+}
+
+void
+guc_composite_scanner_finish(yyscan_t yyscanner)
+{
+ yylex_destroy(yyscanner);
+}
+
+void
+guc_composite_yyerror(void *composite_ptr, const char *composite_type, const char **hintmsg, int flags, yyscan_t yyscanner, const char *message)
+{
+ struct yyguts_t *yyg = (struct yyguts_t *) yyscanner; /* needed for yytext
+ * macro */
+
+ /* report only the first error in a parse operation */
+ if (*hintmsg)
+ return;
+ if (yytext[0])
+ *hintmsg = psprintf("%s at or near \"%s\"", message, yytext);
+ else
+ *hintmsg = psprintf("%s at the end of input", message);
+}
+
+/*
+ * Interface functions to make flex use palloc() instead of malloc().
+ * It'd be better to make these static, but flex insists otherwise.
+ */
+
+void *
+yyalloc(yy_size_t size, yyscan_t yyscanner)
+{
+ return palloc(size);
+}
+
+void *
+yyrealloc(void *ptr, yy_size_t size, yyscan_t yyscanner)
+{
+ if (ptr)
+ return repalloc(ptr, size);
+ else
+ return palloc(size);
+}
+
+void
+yyfree(void *ptr, yyscan_t yyscanner)
+{
+ if (ptr)
+ pfree(ptr);
+}
diff --git a/src/backend/utils/misc/guc_funcs.c b/src/backend/utils/misc/guc_funcs.c
index b9e26982ab..a724fec0fc 100644
--- a/src/backend/utils/misc/guc_funcs.c
+++ b/src/backend/utils/misc/guc_funcs.c
@@ -18,6 +18,7 @@
#include <sys/stat.h>
#include <unistd.h>
+#include "utils/guc.h"
#include "access/xact.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_authid.h"
@@ -55,15 +56,26 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
switch (stmt->kind)
{
+ char *prepared_name;
case VAR_SET_VALUE:
case VAR_SET_CURRENT:
+ if (stmt_has_serialized_composite(stmt))
+ {
+ int name_len = strlen(stmt->name) + 3;
+ prepared_name = guc_malloc(ERROR, name_len);
+ snprintf(prepared_name, name_len, "%s->", stmt->name);
+ }
+ else
+ prepared_name = guc_strdup(ERROR, stmt->name);
+
if (stmt->is_local)
WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
- (void) set_config_option(stmt->name,
+ (void) set_config_option(prepared_name,
ExtractSetVariableArgs(stmt),
(superuser() ? PGC_SUSET : PGC_USERSET),
PGC_S_SESSION,
action, true, 0, false);
+ guc_free(prepared_name);
break;
case VAR_SET_MULTI:
@@ -157,6 +169,42 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
ACL_SET, stmt->kind, false);
}
+/*
+ * Get arguments of set statement and check if some arguments are
+ * serialized composite options
+ */
+bool
+stmt_has_serialized_composite(VariableSetStmt *stmt)
+{
+ List *args;
+ ListCell *l;
+
+ switch (stmt->kind)
+ {
+ case VAR_SET_VALUE:
+ args = stmt->args;
+ /* Fast path if just DEFAULT */
+ if (args == NIL)
+ return false;
+ /* go throw args list and check nodeTags */
+ foreach(l, args)
+ {
+ A_Const *con;
+ Node *arg = (Node *) lfirst(l);
+
+ if (!IsA(arg, A_Const))
+ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(arg));
+ con = (A_Const *) arg;
+
+ if (nodeTag(&con->val) == T_SerializedComposite)
+ return true;
+ }
+ return false;
+ default:
+ return false;
+ }
+}
+
/*
* Get the value to assign for a VariableSetStmt, or NULL if it's RESET.
* The result is palloc'd.
@@ -295,6 +343,10 @@ flatten_set_variable_args(const char *name, List *args)
appendStringInfoString(&buf, val);
}
break;
+ case T_SerializedComposite:
+ val = compositeVal(&con->val);
+ appendStringInfoString(&buf, val);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(&con->val));
@@ -618,8 +670,6 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
/* context */
values[6] = GucContext_Names[conf->context];
- /* vartype */
- values[7] = config_type_names[conf->vartype];
/* source */
values[8] = GucSource_Names[conf->source];
@@ -631,6 +681,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_bool *lconf = (struct config_bool *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -652,6 +705,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_int *lconf = (struct config_int *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
snprintf(buffer, sizeof(buffer), "%d", lconf->min);
values[9] = pstrdup(buffer);
@@ -677,6 +733,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_real *lconf = (struct config_real *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
snprintf(buffer, sizeof(buffer), "%g", lconf->min);
values[9] = pstrdup(buffer);
@@ -702,6 +761,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_string *lconf = (struct config_string *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -729,6 +791,9 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
{
struct config_enum *lconf = (struct config_enum *) conf;
+ /* vartype */
+ values[7] = pstrdup(config_type_names[conf->vartype]);
+
/* min_val */
values[9] = NULL;
@@ -754,6 +819,48 @@ GetConfigOptionValues(struct config_generic *conf, const char **values)
}
break;
+ case PGC_COMPOSITE:
+ {
+ struct config_composite *lconf = (struct config_composite *) conf;
+ int len = (strlen(lconf->type_name) + 8);
+
+ /* vartype */
+ values[7] = palloc(len);
+ snprintf((char *) values[7], len, "%s %s", config_type_names[conf->vartype], lconf->type_name);
+
+ /* min_val */
+ values[9] = NULL;
+
+ /* max_val */
+ values[10] = NULL;
+
+ /* enumvals */
+ values[11] = NULL;
+ /* boot_val */
+ if (lconf->boot_val == NULL)
+ values[12] = NULL;
+ else
+ {
+ bool not_write_to_file = false;
+ char *boot_v = composite_to_str(lconf->boot_val, lconf->type_name, not_write_to_file);
+
+ values[12] = pstrdup(boot_v);
+ guc_free(boot_v);
+ }
+ /* reset_val */
+ if (lconf->reset_val == NULL)
+ values[13] = NULL;
+ else
+ {
+ bool not_write_to_file = false;
+ char *reset = composite_to_str(lconf->reset_val, lconf->type_name, not_write_to_file);
+
+ values[13] = pstrdup(reset);
+ guc_free(reset);
+ }
+ }
+ break;
+
default:
{
/*
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 6bc6be13d2..c392d00972 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -917,6 +917,12 @@
boot_val => 'true',
},
+{ name => 'extended_guc_arrays', type => 'bool', context => 'PGC_USERSET', group => 'CUSTOM_OPTIONS',
+ short_desc => 'Enables expanded view of dynamic arrays in GUC',
+ variable => 'extended_array_view',
+ boot_val => 'false',
+},
+
{ name => 'archive_timeout', type => 'int', context => 'PGC_SIGHUP', group => 'WAL_ARCHIVING',
short_desc => 'Sets the amount of time to wait before forcing a switch to the next WAL file.',
long_desc => '0 disables the timeout.',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 00c8376cf4..c45be54f3e 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -755,10 +755,72 @@ const char *const config_type_names[] =
[PGC_REAL] = "real",
[PGC_STRING] = "string",
[PGC_ENUM] = "enum",
+ [PGC_COMPOSITE] = "composite",
};
-StaticAssertDecl(lengthof(config_type_names) == (PGC_ENUM + 1),
+StaticAssertDecl(lengthof(config_type_names) == (PGC_COMPOSITE + 1),
"array length mismatch");
+/*
+ * Table of user composite types
+ *
+ * See src/backend/utils/misc/README for design notes.
+ *
+ * TO ADD NEW DEFINITION OF COMPOSITE TYPE:
+ *
+ * 1. Decide on a type name
+ *
+ * 2. Add a record below.
+ * Signature (second field of structure) must be in format:
+ * "<type_1> <name_1>;<type_2> <name_2>; ... ; <type_K> <name_K>"
+ * where type_N - one of { bool, int, real, string, <custom_type>}
+ * (custom_type - already defined composite type,
+ * array[<number>] - static array,
+ * array[] - dynamic array)
+ *
+ * HINT. Do not fill 3 - 6 fields, they will be filled automatically
+ * in init_type_definition function
+ *
+ * NOTE. You must change set of functions in guc_composite.c to add
+ * a scalar type. See how built-in types are implemented.
+ */
+struct type_definition UserDefinedConfigureTypes[] = {
+ {
+ "bool",
+ NULL,
+ 0,
+ sizeof(bool),
+ sizeof(bool),
+ NULL
+ },
+ {
+ "int",
+ NULL,
+ 0,
+ sizeof(int),
+ sizeof(int),
+ NULL
+ },
+ {
+ "real",
+ NULL,
+ 0,
+ sizeof(double),
+ sizeof(double),
+ NULL
+ },
+ {
+ "string",
+ NULL,
+ 0,
+ sizeof(char *),
+ sizeof(char *),
+ NULL
+ },
+ /* End-of-list marker */
+ {
+ NULL, NULL
+ }
+};
#include "utils/guc_tables.inc.c"
diff --git a/src/backend/utils/misc/help_config.c b/src/backend/utils/misc/help_config.c
index 55c36ddf05..2722baa262 100644
--- a/src/backend/utils/misc/help_config.c
+++ b/src/backend/utils/misc/help_config.c
@@ -35,6 +35,7 @@ typedef union
struct config_int integer;
struct config_string string;
struct config_enum _enum;
+ struct config_composite _composite;
} mixedStruct;
@@ -125,6 +126,18 @@ printMixedStruct(mixedStruct *structToPrint)
structToPrint->_enum.boot_val));
break;
+ case PGC_COMPOSITE:
+ {
+ bool not_write_to_file = false;
+ char *valstr = NULL;
+
+ valstr = composite_to_str(structToPrint->_composite.boot_val,
+ structToPrint->_composite.type_name,
+ not_write_to_file);
+ printf("COMPSITE %s\t%s\t\t\t", structToPrint->_composite.type_name, valstr);
+ guc_free(valstr);
+ break;
+ }
default:
write_stderr("internal error: unrecognized run-time parameter type\n");
break;
diff --git a/src/backend/utils/misc/meson.build b/src/backend/utils/misc/meson.build
index 9e389a00d0..3a9695af60 100644
--- a/src/backend/utils/misc/meson.build
+++ b/src/backend/utils/misc/meson.build
@@ -2,6 +2,7 @@
backend_sources += files(
'conffiles.c',
+ 'guc_composite.c',
'guc.c',
'guc_funcs.c',
'guc_tables.c',
@@ -20,6 +21,31 @@ backend_sources += files(
'tzparser.c',
)
+# see ../../parser/meson.build
+guc_composite_parser_sources = []
+
+guc_composite_scan = custom_target('guc_composite_scan',
+ input: 'guc_composite_scan.l',
+ output: 'guc_composite_scan.c',
+ command: flex_cmd,
+)
+generated_sources += guc_composite_scan
+guc_composite_parser_sources += guc_composite_scan
+
+guc_composite_gram = custom_target('guc_composite_gram',
+ input: 'guc_composite_gram.y',
+ kwargs: bison_kw,
+)
+generated_sources += guc_composite_gram.to_list()
+guc_composite_parser_sources += guc_composite_gram
+
+backend_link_with += static_library('guc_composite_parser',
+ guc_composite_parser_sources,
+ dependencies: [backend_code],
+ include_directories: include_directories('.'),
+ kwargs: internal_lib_args,
+)
+
guc_scan = custom_target('guc_scan',
input: 'guc-file.l',
output: 'guc-file.c',
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 5473ce9a28..5e5552b3c7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -102,6 +102,7 @@ extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid,
bool summarizing, bool withoutoverlaps);
extern Node *makeStringConst(char *str, int location);
+extern Node *makeSerializedCompositeConst(char *str, int location);
extern DefElem *makeDefElem(char *name, Node *arg, int location);
extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
DefElemAction defaction, int location);
diff --git a/src/include/nodes/value.h b/src/include/nodes/value.h
index 3ee3b976b8..6a1b31c90f 100644
--- a/src/include/nodes/value.h
+++ b/src/include/nodes/value.h
@@ -76,15 +76,25 @@ typedef struct BitString
char *bsval;
} BitString;
+typedef struct SerializedComposite
+{
+ pg_node_attr(special_read_write)
+
+ NodeTag type;
+ char *sval;
+} SerializedComposite;
+
#define intVal(v) (castNode(Integer, v)->ival)
#define floatVal(v) atof(castNode(Float, v)->fval)
#define boolVal(v) (castNode(Boolean, v)->boolval)
#define strVal(v) (castNode(String, v)->sval)
+#define compositeVal(v) (castNode(SerializedComposite, v)->sval)
extern Integer *makeInteger(int i);
extern Float *makeFloat(char *numericStr);
extern Boolean *makeBoolean(bool val);
extern String *makeString(char *str);
extern BitString *makeBitString(char *str);
+extern SerializedComposite *makeSerializedComposite(char *str);
#endif /* VALUE_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index f21ec37da8..b197a81f28 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -185,12 +185,14 @@ typedef bool (*GucIntCheckHook) (int *newval, void **extra, GucSource source);
typedef bool (*GucRealCheckHook) (double *newval, void **extra, GucSource source);
typedef bool (*GucStringCheckHook) (char **newval, void **extra, GucSource source);
typedef bool (*GucEnumCheckHook) (int *newval, void **extra, GucSource source);
+typedef bool (*GucCompositeCheckHook) (void *newval, void **extra, GucSource source);
typedef void (*GucBoolAssignHook) (bool newval, void *extra);
typedef void (*GucIntAssignHook) (int newval, void *extra);
typedef void (*GucRealAssignHook) (double newval, void *extra);
typedef void (*GucStringAssignHook) (const char *newval, void *extra);
typedef void (*GucEnumAssignHook) (int newval, void *extra);
+typedef void (*GucCompositeAssignHook) (void *newval, void *extra);
typedef const char *(*GucShowHook) (void);
@@ -243,6 +245,11 @@ typedef enum
#define GUC_UNIT (GUC_UNIT_MEMORY | GUC_UNIT_TIME)
+/*
+ * Precision with which REAL type guc values are to be printed for GUC
+ * serialization.
+ */
+#define REALTYPE_PRECISION 17
/* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */
extern PGDLLIMPORT bool Debug_print_plan;
@@ -325,6 +332,8 @@ extern PGDLLIMPORT char *role_string;
extern PGDLLIMPORT bool in_hot_standby_guc;
extern PGDLLIMPORT bool trace_sort;
+extern PGDLLIMPORT bool extended_array_view;
+
#ifdef DEBUG_BOUNDED_SORT
extern PGDLLIMPORT bool optimize_bounded_sort;
#endif
@@ -413,6 +422,20 @@ extern void DefineCustomEnumVariable(const char *name,
GucEnumAssignHook assign_hook,
GucShowHook show_hook) pg_attribute_nonnull(1, 4);
+extern void DefineCustomCompositeVariable(const char *name,
+ const char *short_desc,
+ const char *long_desc,
+ const char *type_name,
+ void *valueAddr,
+ const void *bootValueAddr,
+ GucContext context,
+ int flags,
+ GucCompositeCheckHook check_hook,
+ GucCompositeAssignHook assign_hook,
+ GucShowHook show_hook);
+
+extern void DefineCustomCompositeType(const char *type_name, const char *signature);
+
extern void MarkGUCPrefixReserved(const char *className);
/* old name for MarkGUCPrefixReserved, for backwards compatibility: */
@@ -473,6 +496,8 @@ pg_nodiscard extern void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+char *composite_to_str(const void *structp, const char *type, bool writing_to_file);
+
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
extern void read_nondefault_variables(void);
@@ -486,6 +511,7 @@ extern void RestoreGUCState(void *gucstate);
/* Functions exported by guc_funcs.c */
extern void ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel);
extern char *ExtractSetVariableArgs(VariableSetStmt *stmt);
+extern bool stmt_has_serialized_composite(VariableSetStmt *stmt);
extern void SetPGVariable(const char *name, List *args, bool is_local);
extern void GetPGVariable(const char *name, DestReceiver *dest);
extern TupleDesc GetPGVariableResultDesc(const char *name);
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index f72ce944d7..efe85fd80f 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -27,6 +27,7 @@ enum config_type
PGC_REAL,
PGC_STRING,
PGC_ENUM,
+ PGC_COMPOSITE,
};
union config_var_val
@@ -36,6 +37,7 @@ union config_var_val
double realval;
char *stringval;
int enumval;
+ void *compositeval;
};
/*
@@ -298,18 +300,56 @@ struct config_enum
void *reset_extra;
};
+/* work with type signatures */
+
+typedef struct struct_field
+{
+ char *type;
+ char *name;
+} struct_field;
+
+struct type_definition
+{
+ /* constant fields */
+ const char *type_name;
+ const char *signature;
+ int cnt_fields;
+ int type_size;
+ int offset;
+ struct_field *fields;
+};
+
+struct config_composite
+{
+ struct config_generic gen;
+ /* constant fields, must be set correctly in initial value: */
+ const char *type_name;
+ void *variable;
+ const void *boot_val;
+ GucCompositeCheckHook check_hook;
+ GucCompositeAssignHook assign_hook;
+ GucShowHook show_hook;
+ /* variable fields, initialized at runtime: */
+ void *reset_val;
+ void *reset_extra;
+ const struct type_definition *definition;
+};
+
/* constant tables corresponding to enums above and in guc.h */
extern PGDLLIMPORT const char *const config_group_names[];
extern PGDLLIMPORT const char *const config_type_names[];
extern PGDLLIMPORT const char *const GucContext_Names[];
extern PGDLLIMPORT const char *const GucSource_Names[];
+/* array defining all the built-in composite types for GUC variables */
+extern PGDLLIMPORT struct type_definition UserDefinedConfigureTypes[];
/* data arrays defining all the built-in GUC variables */
extern PGDLLIMPORT struct config_bool ConfigureNamesBool[];
extern PGDLLIMPORT struct config_int ConfigureNamesInt[];
extern PGDLLIMPORT struct config_real ConfigureNamesReal[];
extern PGDLLIMPORT struct config_string ConfigureNamesString[];
extern PGDLLIMPORT struct config_enum ConfigureNamesEnum[];
+extern PGDLLIMPORT struct config_composite ConfigureNamesComposite[];
/* lookup GUC variables, returning config_generic pointers */
extern struct config_generic *find_option(const char *name,
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index f22ca213c2..3608453e6d 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -260,6 +260,10 @@ sub main
s/{/ { /g;
s/}/ } /g;
+ # Make exclusion for '{' and '}'
+ s/' \{ '/'{'/g;
+ s/' \} '/'}'/g;
+
# Likewise for comment start/end markers
s|\/\*| /* |g;
s|\*\/| */ |g;
--
2.48.1
Hi
what is use case for this?
Regards
Pavel
po 22. 9. 2025 v 8:55 odesílatel Чумак Антон <a.chumak@postgrespro.ru>
napsal:
Show quoted text
Hello hackers,
The new version of the patch adds support for multi-line writing of
composite type values in the postgresql.conf file. Hidden fields have also
been added. Such fields may be required to protect the private part of the
state of a composite option from an external user. In order for the field
to be hidden, the composite type signature must describe only the field
type without the field name.Please note that all allocated resources used within hidden fields should
use only guc_malloc. This is necessary to automatically release resources.The patch applies cleanly to the master
(9fc7f6ab7226d7c9dbe4ff333130c82f92749f69)Best regards,
Anton Chumak
Thank you for your question!
Composite parameters in a configuration system are needed to describe complex objects that have many interrelated parameters. Such examples already exist in PostgreSQL: synchronous_standby_names or primary_conninfo. And with these parameters, there are some difficulties for both developers and DBMS administrators.
For administrators: * The value of such parameters can only be written in full as a string and there is no way to access individual fields or substructure. * Each such parameter has its own syntax (compare the syntax description of synchronous_standby_names and primary_conninfo)
For developers: * For each composite parameter, you need to write your own parser that will parse the string value, instead of just describing the logic.
Personally, I needed to describe the cluster configuration. A cluster consists of nodes interconnected by some logic. And it turns out that in the current system, I need to write 1 more parser for this parameter, and the user will have to learn 1 more syntax.
This patch creates a unified approach to creating composite options, provides a unified syntax for values of composite types, adds the ability to work with fields and substructures, and eliminates the need for developers to write their own parsers for each composite parameter.
Best regards
Anton Chumak
Import Notes
Resolved by subject fallback
Hi
po 22. 9. 2025 v 11:13 odesílatel Чумак Антон <a.chumak@postgrespro.ru>
napsal:
Thank you for your question!
Composite parameters in a configuration system are needed to describe
complex objects that have many interrelated parameters. Such examples
already exist in PostgreSQL: synchronous_standby_names or primary_conninfo.
And with these parameters, there are some difficulties for both developers
and DBMS administrators.
Do we really need this?
synchronous_standby_names is a simple list and primary_conninfo is just a
string - consistent with any other postgresql connection string.
If you need to store more complex values, why you don't use integrated json
parser?
I don't like you introduce new independent language just for GUC and this
is not really short (and it is partially redundant to json). Currently
working with GUC is simple, because supported operations and formats are
simple.
Regards
Pavel
For administrators:
1. The value of such parameters can only be written in full as a
string and there is no way to access individual fields or substructure.
2. Each such parameter has its own syntax (compare the syntax
description of synchronous_standby_names and primary_conninfo)For developers:
1. For each composite parameter, you need to write your own parser
that will parse the string value, instead of just describing the logic.Personally, I needed to describe the cluster configuration. A cluster
consists of nodes interconnected by some logic. And it turns out that in
the current system, I need to write 1 more parser for this parameter, and
the user will have to learn 1 more syntax.This patch creates a unified approach to creating composite options,
provides a unified syntax for values of composite types, adds the ability
to work with fields and substructures, and eliminates the need for
developers to write their own parsers for each composite parameter.
looks like overengineering for me - when you have complex configuration -
isn't better to use table? Or json value - if you need to store all to one
GUC.
Regards
Pavel
Show quoted text
Best regards
Anton Chumak
po 22. 9. 2025 v 12:03 odesílatel Pavel Stehule <pavel.stehule@gmail.com>
napsal:
Hi
po 22. 9. 2025 v 11:13 odesílatel Чумак Антон <a.chumak@postgrespro.ru>
napsal:Thank you for your question!
Composite parameters in a configuration system are needed to describe
complex objects that have many interrelated parameters. Such examples
already exist in PostgreSQL: synchronous_standby_names or primary_conninfo.
And with these parameters, there are some difficulties for both developers
and DBMS administrators.Do we really need this?
synchronous_standby_names is a simple list and primary_conninfo is just a
string - consistent with any other postgresql connection string.If you need to store more complex values, why you don't use integrated
json parser?I don't like you introduce new independent language just for GUC and this
is not really short (and it is partially redundant to json). Currently
working with GUC is simple, because supported operations and formats are
simple.
Another issue is using symbols -> for dereferencing directly from the
scanner. It can break applications that use the same symbols as a custom
operator.
Regards
Pavel
Show quoted text
Regards
Pavel
For administrators:
1. The value of such parameters can only be written in full as a
string and there is no way to access individual fields or substructure.
2. Each such parameter has its own syntax (compare the syntax
description of synchronous_standby_names and primary_conninfo)For developers:
1. For each composite parameter, you need to write your own parser
that will parse the string value, instead of just describing the logic.Personally, I needed to describe the cluster configuration. A cluster
consists of nodes interconnected by some logic. And it turns out that in
the current system, I need to write 1 more parser for this parameter, and
the user will have to learn 1 more syntax.This patch creates a unified approach to creating composite options,
provides a unified syntax for values of composite types, adds the ability
to work with fields and substructures, and eliminates the need for
developers to write their own parsers for each composite parameter.looks like overengineering for me - when you have complex configuration -
isn't better to use table? Or json value - if you need to store all to one
GUC.Regards
Pavel
Best regards
Anton Chumak
=?utf-8?q?=D0=A7=D1=83=D0=BC=D0=B0=D0=BA_=D0=90=D0=BD=D1=82=D0=BE=D0=BD?= <a.chumak@postgrespro.ru> writes:
This patch adds a unified mechanism for declaring and using composite configuration options in GUC, eliminating the need to write a custom parser for each new complex data type. New syntax for end user is json-like.
TBH, I think this is a bad idea altogether. GUCs that would need
this are probably poorly designed in the first place; we should not
encourage inventing more. I also don't love adding thousands of
lines of code without any use-case at hand.
regards, tom lane
On Monday, September 22, 2025, Tom Lane <tgl@sss.pgh.pa.us> wrote:
=?utf-8?q?=D0=A7=D1=83=D0=BC=D0=B0=D0=BA_=D0=90=D0=BD=D1=82=D0=BE=D0=BD?=
<a.chumak@postgrespro.ru> writes:This patch adds a unified mechanism for declaring and using composite
configuration options in GUC, eliminating the need to write a custom parser
for each new complex data type. New syntax for end user is json-like.TBH, I think this is a bad idea altogether. GUCs that would need
this are probably poorly designed in the first place; we should not
encourage inventing more. I also don't love adding thousands of
lines of code without any use-case at hand.
Yeah, there is a decent height bar for me too. The main functional benefit
we’d get is that since both (multiple) settings are being given values
simultaneously the check option code can enforce that only valid
combinations are ever specified instead of generally needing runtime checks.
Beyond that, just use separate options with a naming scheme.
I can maybe see this for session variables masquerading as GUCs since we
lack the former. Something like wanting to store a JWT as-is in a GUC then
referencing its components.
David J.
Sorry, I replied to the email without the hackers tag, so some of our correspondence was not saved on hackers. Therefore, I will quote my answer and Pavel's questions and remarks below.
Thank you for your question!
Composite parameters in a configuration system are needed to describe complex objects that have many interrelated parameters. Such examples already exist in PostgreSQL: synchronous_standby_names or primary_conninfo. And with these parameters, there are some difficulties for both developers and DBMS administrators.Do we really need this?
synchronous_standby_names is a simple list and primary_conninfo is just a string - consistent with any other postgresql connection string.
synchronous_standby_names is somewhat more complicated than a regular list. Its first field is the mode, the second is the number of required replicas, and only then is the list. Note its check hook. A parser is called there, whose code length exceeds the rest of the logic associated with this parameter. This is exactly the kind of problem the patch solves.
If you need to store more complex values, why you don't use integrated json parser?
I don't like you introduce new independent language just for GUC and this is not really short (and it is partially redundant to json). Currently working with GUC is simple, because supported operations and formats are simple.
I looked at the json value parsing function with the ability to use custom semantic actions, and it might be a really great idea to use it instead of a self-written parser. Then the composite values will have the standard json syntax, and the patch will probably decrease in size.
For administrators:
1. The value of such parameters can only be written in full as a string and there is no way to access individual fields or substructure.
2. Each such parameter has its own syntax (compare the syntax description of synchronous_standby_names and primary_conninfo)
For developers:
1. For each composite parameter, you need to write your own parser that will parse the string value, instead of just describing the logic.
Personally, I needed to describe the cluster configuration. A cluster consists of nodes interconnected by some logic. And it turns out that in the current system, I need to write 1 more parser for this parameter, and the user will have to learn 1 more syntax.
This patch creates a unified approach to creating composite options, provides a unified syntax for values of composite types, adds the ability to work with fields and substructures, and eliminates the need for developers to write their own parsers for each composite parameterlooks like overengineering for me - when you have complex configuration - isn't better to use table? Or json value - if you need to store all to one GUC.
Tables are not suitable for storing configuration, because we need GUC capabilities such as analyzing the source of a new value, working at the time of postmaster startup, SET LOCAL support, etc.
Another issue is using symbols -> for dereferencing directly from the scanner. It can break applications that use the same symbols as a custom operator.
I made the dereference operator look like -> because the dot is already used to separate the class of names from options. It is possible to use a dot, but then we need to agree that composite parameters and extensions must not have the same names in order to avoid collisions.
Best regards
Anton Chumak
Hi
po 22. 9. 2025 v 23:31 odesílatel David G. Johnston <
david.g.johnston@gmail.com> napsal:
On Monday, September 22, 2025, Tom Lane <tgl@sss.pgh.pa.us> wrote:
=?utf-8?q?=D0=A7=D1=83=D0=BC=D0=B0=D0=BA_=D0=90=D0=BD=D1=82=D0=BE=D0=BD?=
<a.chumak@postgrespro.ru> writes:This patch adds a unified mechanism for declaring and using composite
configuration options in GUC, eliminating the need to write a custom parser
for each new complex data type. New syntax for end user is json-like.TBH, I think this is a bad idea altogether. GUCs that would need
this are probably poorly designed in the first place; we should not
encourage inventing more. I also don't love adding thousands of
lines of code without any use-case at hand.Yeah, there is a decent height bar for me too. The main functional
benefit we’d get is that since both (multiple) settings are being given
values simultaneously the check option code can enforce that only valid
combinations are ever specified instead of generally needing runtime checks.Beyond that, just use separate options with a naming scheme.
I can maybe see this for session variables masquerading as GUCs since we
lack the former. Something like wanting to store a JWT as-is in a GUC then
referencing its components.
Using GUC as session variables is a workaround because there is nothing
better. But it is not good solution
- it doesn't to native format for data - all have to be text
- composites are not compatible with native composites, arrays are not
compatible with native composites
- the GUC cannot be used simply in queries or expressions, and cannot be
set as a result of query or expression
- the GUC are not stable, so there can be some unwanted artefacts inside
queries
This patch has about 40% size like my session variables patches - and
significantly less user documentation, significantly less regress tests -
it does something else.
The basic question is if variables should be typed or typeless - like
plpgsql or psql variables. Typeless variables are more simple on
implementation, but a) can be too primitive to store some more complex
structures, or b) are not simple on implementation - and the complexity is
very similar to typed variables. When variables are typed, then it is
necessary to modify plan cache invalidation (what is not necessary for
typeless variables). With its own catalog, the enhancing plan cache
invalidation is simple and clean (because invalidation of plan cache is
based on watching catalog changes), without catalog the plan cache
invalidation is much more dirty work. Another question is the possibility
to set different defaults for GUC by ALTER command. It can be an
interesting, but very dangerous feature for session variables.
GUC are great for configuration and holding possibly different defaults for
role, database or system. PostgreSQL configuration is large, but it uses a
very simple format, and I think it is working well.
Regards
Pavel
Show quoted text
David J.
út 23. 9. 2025 v 5:38 odesílatel Чумак Антон <a.chumak@postgrespro.ru>
napsal:
Sorry, I replied to the email without the hackers tag, so some of our
correspondence was not saved on hackers. Therefore, I will quote my answer
and Pavel's questions and remarks below.Thank you for your question!
Composite parameters in a configuration system are needed to describecomplex objects that have many interrelated parameters. Such examples
already exist in PostgreSQL: synchronous_standby_names or primary_conninfo.
And with these parameters, there are some difficulties for both developers
and DBMS administrators.Do we really need this?
synchronous_standby_names is a simple list and primary_conninfo is just astring - consistent with any other postgresql connection string.
synchronous_standby_names is somewhat more complicated than a regular list
. Its first field is the mode, the second is the number of required
replicas, and only then is the list. Note its check hook. A parser is
called there, whose code length exceeds the rest of the logic associated
with this parameter. This is exactly the kind of problem the patch solves.If you need to store more complex values, why you don't use integrated
json parser?
I don't like you introduce new independent language just for GUC and this
is not really short (and it is partially redundant to json). Currently
working with GUC is simple, because supported operations and formats are
simple.I looked at the json value parsing function with the ability to use custom
semantic actions, and it might be a really great idea to use it instead
of a self-written parser. Then the composite values will have the standard
json syntax, and the patch will probably decrease in size.
when you use json, then what is the benefit from your patch?
It is not too big difference if I set value by SET command or by SELECT
set_config()
for some complex configuration I don't think so the best way is a direct
modification of some complex value. You still need some helper functions,
and these functions can hide all complexity. More - the performance there
is not important, so plpgsql can be used well - and working with json in
plpgsql is almost comfortable.
Show quoted text
For administrators:
1. The value of such parameters can only be written in full as a stringand there is no way to access individual fields or substructure.
2. Each such parameter has its own syntax (compare the syntax
description of synchronous_standby_names and primary_conninfo)
For developers:
1. For each composite parameter, you need to write your own parser thatwill parse the string value, instead of just describing the logic.
Personally, I needed to describe the cluster configuration. A cluster
consists of nodes interconnected by some logic. And it turns out that in
the current system, I need to write 1 more parser for this parameter, and
the user will have to learn 1 more syntax.This patch creates a unified approach to creating composite options,
provides a unified syntax for values of composite types, adds the ability
to work with fields and substructures, and eliminates the need for
developers to write their own parsers for each composite parameterlooks like overengineering for me - when you have complex configuration -
isn't better to use table? Or json value - if you need to store all to one
GUC.Tables are not suitable for storing configuration, because we need GUC
capabilities such as analyzing the source of a new value, working at the
time of postmaster startup, SET LOCAL support, etc.Another issue is using symbols -> for dereferencing directly from the
scanner. It can break applications that use the same symbols as a custom
operator.I made the dereference operator look like -> because the dot is already
used to separate the class of names from options. It is possible to use a
dot, but then we need to agree that composite parameters and extensions
must not have the same names in order to avoid collisions.Best regards
Anton Chumak
Pavel Stehule <pavel.stehule@gmail.com> writes:
Using GUC as session variables is a workaround because there is nothing
better. But it is not good solution
Agreed, but we don't yet have a better one ...
The basic question is if variables should be typed or typeless - like
plpgsql or psql variables.
I think it is absolutely critical that GUCs *not* depend on the
SQL type system in any way. That would be a fundamental layering
violation, because we need to be able to read postgresql.conf
before we can read catalogs --- not to mention that relevant type
definitions might be different in different databases.
I'm not sure that this point means much to the feature proposed in
this thread, since IIUC it's proposing "use JSON no matter what".
But it is a big problem for trying to use GUCs as session variables
with non-built-in types.
regards, tom lane
út 23. 9. 2025 v 5:50 odesílatel Tom Lane <tgl@sss.pgh.pa.us> napsal:
Pavel Stehule <pavel.stehule@gmail.com> writes:
Using GUC as session variables is a workaround because there is nothing
better. But it is not good solutionAgreed, but we don't yet have a better one ...
but better GUC will not be good session variables
The basic question is if variables should be typed or typeless - like
plpgsql or psql variables.I think it is absolutely critical that GUCs *not* depend on the
SQL type system in any way. That would be a fundamental layering
violation, because we need to be able to read postgresql.conf
before we can read catalogs --- not to mention that relevant type
definitions might be different in different databases.I'm not sure that this point means much to the feature proposed in
this thread, since IIUC it's proposing "use JSON no matter what".
But it is a big problem for trying to use GUCs as session variables
with non-built-in types.
This is a very important note - and it clearly describes the advantages
(and sense) of GUC.
GUC is good enough for work with text types - and our json is text type
based.
I can serialize and deserialize any array or composite to GUC but it has no
"nice" output in the SHOW command.
I can imagine an idea so the SET command can be able to evaluate simple
expressions (like arguments of CALL statements).
But it is not possible without a compatibility break. So right side of SET
command will be always reduced to value or list, and
I don't see a strong benefit to enhancing it gently.
Show quoted text
regards, tom lane
when you use json, then what is the benefit from your patch?
json is just a syntax. This is only part of the patch. The main feature is that we can directly, in a standard way, without the efforts of developers, translate composite values from user interfaces like psql or postgresql.conf into structures in C code. With this patch, the configuration system gains the ability to correctly manage the state of composite objects. This is important when you need to change 2 out of 5 fields at the same time so that the structure remains consistent. In addition, the new configuration module takes over the management of resources within the framework, which can be important for strings and dynamic arrays. There are other auxiliary features like hidden fields.
It is not too big difference if I set value by SET command or by SELECT set_config()
Working with parameters is not limited to working within a session, otherwise the PGC_INTERNAL, PGC_POSTMASTER, and PGC_SIGHUP contexts would not be needed. My patch provides unified support for composite types and within such contexts. Example: you have a composite boot value and in the postgresql.conf file you need to change only 2 fields, and you need to do this at the same time to maintain the consistency of the structure. Now you would have to describe all the fields in one big line, and with the patch you can only describe the changed fields.
Best regards
Anton Chumak
út 23. 9. 2025 v 6:33 odesílatel Чумак Антон <a.chumak@postgrespro.ru>
napsal:
when you use json, then what is the benefit from your patch?
json is just a syntax. This is only part of the patch. The main feature is
that we can directly, in a standard way, without the efforts of developers,
translate composite values from user interfaces like psql or
postgresql.conf into structures in C code. With this patch, the
configuration system gains the ability to correctly manage the state of
composite objects. This is important when you need to change 2 out of 5
fields at the same time so that the structure remains consistent. In
addition, the new configuration module takes over the management of
resources within the framework, which can be important for strings and
dynamic arrays. There are other auxiliary features like hidden fields.
How common are composites in configuration? It goes against the simplicity
of configuration.
And if you really need it - you can use plpgsql code and set_config
function.
It is not too big difference if I set value by SET command or by SELECT
set_config()
Working with parameters is not limited to working within a session,
otherwise the PGC_INTERNAL, PGC_POSTMASTER, and PGC_SIGHUP contexts would
not be needed. My patch provides unified support for composite types and
within such contexts. Example: you have a composite boot value and in the
postgresql.conf file you need to change only 2 fields, and you need to do
this at the same time to maintain the consistency of the structure. Now you
would have to describe all the fields in one big line, and with the patch
you can only describe the changed fields.
your patch does just parsing. At the end, you still need to validate values.
Show quoted text
Best regards
Anton Chumak
On Mon, Sep 22, 2025 at 10:33 PM Чумак Антон <a.chumak@postgrespro.ru>
wrote:
Working with parameters is not limited to working within a session,
otherwise the PGC_INTERNAL, PGC_POSTMASTER, and PGC_SIGHUP contexts would
not be needed. My patch provides unified support for composite types and
within such contexts. Example: you have a composite boot value and in the
postgresql.conf file you need to change only 2 fields, and you need to do
this at the same time to maintain the consistency of the structure. Now you
would have to describe all the fields in one big line, and with the patch
you can only describe the changed fields.
You might wish to try an approach where people who do think such a thing
might be useful can voice their support for it rather than trying to
convince people who don't presently see any immediate use cases that there
are some (without saying what those use cases are...).
In short - post to -general.
As you note - moving runtime checks to "SET" time has value and this patch
brings that value. But it is not evident there is enough value to take on
the added complexity. There are few to no requests asking for this ability.
David J.
"David G. Johnston" <david.g.johnston@gmail.com> writes:
As you note - moving runtime checks to "SET" time has value and this patch
brings that value. But it is not evident there is enough value to take on
the added complexity. There are few to no requests asking for this ability.
If anything, I'd say we have decades of experience showing that early
checking of GUC values creates more problems than it solves. There
are too many cases where necessary context is not available at the
time of setting the value. Particularly, CREATE FUNCTION ... SET
and ALTER DATABASE/USER ... SET are problematic for this.
regards, tom lane