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 <akshay.joshi@enterprisedb.com>
Co-authored-by: Andrew Dunstan <andrew@dunslane.net>
Co-authored-by: Euler Taveira <euler@eulerto.com>
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
This commit is contained in:
Andrew Dunstan 2026-03-19 09:50:41 -04:00
parent caec9d9fad
commit 4881981f92
4 changed files with 280 additions and 1 deletions

View file

@ -31,6 +31,7 @@ OBJS = \
datetime.o \
datum.o \
dbsize.o \
ddlutils.o \
domains.o \
encode.o \
enum.o \

View file

@ -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 <name> 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);
}

View file

@ -30,6 +30,7 @@ backend_sources += files(
'datetime.c',
'datum.c',
'dbsize.c',
'ddlutils.c',
'domains.c',
'encode.c',
'enum.c',

View file

@ -631,12 +631,14 @@ DSMREntryType
DSMRegistryCtxStruct
DSMRegistryEntry
DWORD
DataChecksumsStateStruct
DataChecksumsWorkerDatabase
DataChecksumsWorkerResult
DataChecksumsStateStruct
DataDirSyncMethod
DataDumperPtr
DataPageDeleteStack
DdlOptType
DdlOption
DataTypesUsageChecks
DataTypesUsageVersionCheck
DatabaseInfo