From 4881981f92024e4db6249bd3dc96a3859638a665 Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Thu, 19 Mar 2026 09:50:41 -0400 Subject: [PATCH] Add infrastructure for pg_get_*_ddl functions Add parse_ddl_options(), append_ddl_option(), and append_guc_value() helper functions in a new ddlutils.c file that provide common option parsing and output formatting for the pg_get_*_ddl family of functions which will follow in later patches. These accept VARIADIC text arguments as alternating name/value pairs. Callers declare an array of DdlOption descriptors specifying the accepted option names and their types (boolean, text, or integer). parse_ddl_options() matches each supplied pair against the array, validates the value, and fills in the result fields. This descriptor-based scheme is based on an idea from Euler Taveira. This is placed in a new ddlutils.c file which will contain the pg_get_*_ddl functions. Author: Akshay Joshi Co-authored-by: Andrew Dunstan Co-authored-by: Euler Taveira Discussion: https://postgr.es/m/CAKWEB6rmnmGKUA87Zmq-s=b3Scsnj02C0kObQjnbL2ajfPWGEw@mail.gmail.com Discussion: https://postgr.es/m/4c5f895e-3281-48f8-b943-9228b7da6471@gmail.com Discussion: https://postgr.es/m/CANxoLDc6FHBYJvcgOnZyS+jF0NUo3Lq_83-rttBuJgs9id_UDg@mail.gmail.com Discussion: https://postgr.es/m/e247c261-e3fb-4810-81e0-a65893170e94@dunslane.net --- src/backend/utils/adt/Makefile | 1 + src/backend/utils/adt/ddlutils.c | 275 ++++++++++++++++++++++++++++++ src/backend/utils/adt/meson.build | 1 + src/tools/pgindent/typedefs.list | 4 +- 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 src/backend/utils/adt/ddlutils.c diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile index a8fd680589f..0c7621957c1 100644 --- a/src/backend/utils/adt/Makefile +++ b/src/backend/utils/adt/Makefile @@ -31,6 +31,7 @@ OBJS = \ datetime.o \ datum.o \ dbsize.o \ + ddlutils.o \ domains.o \ encode.o \ enum.o \ diff --git a/src/backend/utils/adt/ddlutils.c b/src/backend/utils/adt/ddlutils.c new file mode 100644 index 00000000000..4bf7d9c38ae --- /dev/null +++ b/src/backend/utils/adt/ddlutils.c @@ -0,0 +1,275 @@ +/*------------------------------------------------------------------------- + * + * ddlutils.c + * Utility functions for generating DDL statements + * + * This file contains the pg_get_*_ddl family of functions that generate + * DDL statements to recreate database objects such as roles, tablespaces, + * and databases, along with common infrastructure for option parsing and + * pretty-printing. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/adt/ddlutils.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/varlena.h" + +/* Option value types for DDL option parsing */ +typedef enum +{ + DDL_OPT_BOOL, + DDL_OPT_TEXT, + DDL_OPT_INT, +} DdlOptType; + +/* + * A single DDL option descriptor: caller fills in name and type, + * parse_ddl_options fills in isset + the appropriate value field. + */ +typedef struct DdlOption +{ + const char *name; /* option name (case-insensitive match) */ + DdlOptType type; /* expected value type */ + bool isset; /* true if caller supplied this option */ + /* fields for specific option types */ + union + { + bool boolval; /* filled in for DDL_OPT_BOOL */ + char *textval; /* filled in for DDL_OPT_TEXT (palloc'd) */ + int intval; /* filled in for DDL_OPT_INT */ + }; +} DdlOption; + + +static void parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start, + DdlOption *opts, int nopts); +static void append_ddl_option(StringInfo buf, bool pretty, int indent, + const char *fmt,...) + pg_attribute_printf(4, 5); +static void append_guc_value(StringInfo buf, const char *name, + const char *value); + + +/* + * parse_ddl_options + * Parse variadic name/value option pairs + * + * Options are passed as alternating key/value text pairs. The caller + * provides an array of DdlOption descriptors specifying the accepted + * option names and their types; this function matches each supplied + * pair against the array, validates the value, and fills in the + * result fields. + */ +static void +parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start, + DdlOption *opts, int nopts) +{ + Datum *args; + bool *nulls; + Oid *types; + int nargs; + + /* Clear all output fields */ + for (int i = 0; i < nopts; i++) + { + opts[i].isset = false; + switch (opts[i].type) + { + case DDL_OPT_BOOL: + opts[i].boolval = false; + break; + case DDL_OPT_TEXT: + opts[i].textval = NULL; + break; + case DDL_OPT_INT: + opts[i].intval = 0; + break; + } + } + + nargs = extract_variadic_args(fcinfo, variadic_start, true, + &args, &types, &nulls); + + if (nargs <= 0) + return; + + /* Handle DEFAULT NULL case */ + if (nargs == 1 && nulls[0]) + return; + + if (nargs % 2 != 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("variadic arguments must be name/value pairs"), + errhint("Provide an even number of variadic arguments that can be divided into pairs."))); + + /* + * For each option name/value pair, find corresponding positional option + * for the option name, and assign the option value. + */ + for (int i = 0; i < nargs; i += 2) + { + char *name; + char *valstr; + DdlOption *opt = NULL; + + if (nulls[i]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option name at variadic position %d is null", i + 1))); + + name = TextDatumGetCString(args[i]); + + if (nulls[i + 1]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("value for option \"%s\" must not be null", name))); + + /* Find matching option descriptor */ + for (int j = 0; j < nopts; j++) + { + if (pg_strcasecmp(name, opts[j].name) == 0) + { + opt = &opts[j]; + break; + } + } + + if (opt == NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized option: \"%s\"", name))); + + if (opt->isset) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("option \"%s\" is specified more than once", + name))); + + valstr = TextDatumGetCString(args[i + 1]); + + switch (opt->type) + { + case DDL_OPT_BOOL: + if (!parse_bool(valstr, &opt->boolval)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for boolean option \"%s\": %s", + name, valstr))); + break; + + case DDL_OPT_TEXT: + opt->textval = valstr; + valstr = NULL; /* don't pfree below */ + break; + + case DDL_OPT_INT: + { + char *endp; + long val; + + errno = 0; + val = strtol(valstr, &endp, 10); + if (*endp != '\0' || errno == ERANGE || + val < PG_INT32_MIN || val > PG_INT32_MAX) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for integer option \"%s\": %s", + name, valstr))); + opt->intval = (int) val; + } + break; + } + + opt->isset = true; + + if (valstr) + pfree(valstr); + pfree(name); + } +} + +/* + * Helper to append a formatted string with optional pretty-printing. + */ +static void +append_ddl_option(StringInfo buf, bool pretty, int indent, + const char *fmt,...) +{ + if (pretty) + { + appendStringInfoChar(buf, '\n'); + appendStringInfoSpaces(buf, indent); + } + else + appendStringInfoChar(buf, ' '); + + for (;;) + { + va_list args; + int needed; + + va_start(args, fmt); + needed = appendStringInfoVA(buf, fmt, args); + va_end(args); + if (needed == 0) + break; + enlargeStringInfo(buf, needed); + } +} + +/* + * append_guc_value + * Append a GUC setting value to buf, handling GUC_LIST_QUOTE properly. + * + * Variables marked GUC_LIST_QUOTE were already fully quoted before they + * were stored in the setconfig array. We break the list value apart + * and re-quote the elements as string literals. For all other variables + * we simply quote the value as a single string literal. + * + * The caller has already appended "SET TO " to buf. + */ +static void +append_guc_value(StringInfo buf, const char *name, const char *value) +{ + char *rawval; + + rawval = pstrdup(value); + + if (GetConfigOptionFlags(name, true) & GUC_LIST_QUOTE) + { + List *namelist; + bool first = true; + + /* Parse string into list of identifiers */ + if (!SplitGUCList(rawval, ',', &namelist)) + { + /* this shouldn't fail really */ + elog(ERROR, "invalid list syntax in setconfig item"); + } + /* Special case: represent an empty list as NULL */ + if (namelist == NIL) + appendStringInfoString(buf, "NULL"); + foreach_ptr(char, curname, namelist) + { + if (first) + first = false; + else + appendStringInfoString(buf, ", "); + appendStringInfoString(buf, quote_literal_cstr(curname)); + } + list_free(namelist); + } + else + appendStringInfoString(buf, quote_literal_cstr(rawval)); + + pfree(rawval); +} diff --git a/src/backend/utils/adt/meson.build b/src/backend/utils/adt/meson.build index fb8294d7e4a..d793f8145f6 100644 --- a/src/backend/utils/adt/meson.build +++ b/src/backend/utils/adt/meson.build @@ -30,6 +30,7 @@ backend_sources += files( 'datetime.c', 'datum.c', 'dbsize.c', + 'ddlutils.c', 'domains.c', 'encode.c', 'enum.c', diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c72f6c59573..0c5493bd47f 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -631,12 +631,14 @@ DSMREntryType DSMRegistryCtxStruct DSMRegistryEntry DWORD +DataChecksumsStateStruct DataChecksumsWorkerDatabase DataChecksumsWorkerResult -DataChecksumsStateStruct DataDirSyncMethod DataDumperPtr DataPageDeleteStack +DdlOptType +DdlOption DataTypesUsageChecks DataTypesUsageVersionCheck DatabaseInfo