=== Applying patches on top of PostgreSQL commit ID 32e4508db27d1fa3dc07404ad69d43ed97870f15 ===
/etc/rc.d/jail: WARNING: Per-jail configuration via jail_* variables is obsolete. Please consider migrating to /etc/jail.conf.
Fri Jul 17 12:43:27 UTC 2026
On branch cf/6215
nothing to commit, working tree clean
=== using 'git am' to apply patch ./v15-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch ===
Applying: Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statements.
Using index info to reconstruct a base tree...
M doc/src/sgml/func/func-info.sgml
M src/backend/catalog/system_functions.sql
M src/backend/utils/adt/Makefile
M src/backend/utils/adt/meson.build
M src/backend/utils/adt/ruleutils.c
M src/include/catalog/pg_proc.dat
M src/tools/pgindent/typedefs.list
Falling back to patching base and 3-way merge...
Auto-merging src/tools/pgindent/typedefs.list
Auto-merging src/include/catalog/pg_proc.dat
Auto-merging src/backend/utils/adt/ruleutils.c
Auto-merging src/backend/utils/adt/meson.build
CONFLICT (add/add): Merge conflict in src/backend/utils/adt/ddlutils.c
Auto-merging src/backend/utils/adt/ddlutils.c
Auto-merging src/backend/utils/adt/Makefile
Auto-merging src/backend/catalog/system_functions.sql
Auto-merging doc/src/sgml/func/func-info.sgml
CONFLICT (content): Merge conflict in doc/src/sgml/func/func-info.sgml
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0001 Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statements.
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
=== using patch(1) to apply patch ./v15-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch ===
patching file doc/src/sgml/func/func-info.sgml
Hunk #1 succeeded at 3966 (offset 121 lines).
patching file src/backend/catalog/system_functions.sql
Hunk #1 succeeded at 366 (offset -6 lines).
patching file src/backend/utils/adt/Makefile
Hunk #1 succeeded at 103 (offset 1 line).
The next patch would create the file src/backend/utils/adt/ddlutils.c,
which already exists! Skipping patch.
1 out of 1 hunk ignored
patching file src/backend/utils/adt/meson.build
Hunk #1 succeeded at 99 (offset 1 line).
patching file src/backend/utils/adt/ruleutils.c
Hunk #1 succeeded at 62 (offset 5 lines).
Hunk #2 succeeded at 560 (offset 12 lines).
patching file src/include/catalog/pg_proc.dat
Hunk #1 succeeded at 4047 (offset 13 lines).
patching file src/test/regress/expected/database.out
patching file src/test/regress/sql/database.sql
patching file src/tools/pgindent/typedefs.list
Hunk #1 succeeded at 619 with fuzz 2 (offset 17 lines).
Unstaged changes after reset:
M doc/src/sgml/func/func-info.sgml
M src/backend/catalog/system_functions.sql
M src/backend/utils/adt/Makefile
M src/backend/utils/adt/meson.build
M src/backend/utils/adt/ruleutils.c
M src/include/catalog/pg_proc.dat
M src/test/regress/expected/database.out
M src/test/regress/sql/database.sql
M src/tools/pgindent/typedefs.list
=== using 'git apply' to apply patch ./v15-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch ===
Applied patch to 'doc/src/sgml/func/func-info.sgml' with conflicts.
Applied patch to 'src/backend/catalog/system_functions.sql' cleanly.
Applied patch to 'src/backend/utils/adt/Makefile' cleanly.
Performing three-way merge...
Applied patch to 'src/backend/utils/adt/ddlutils.c' with conflicts.
Applied patch to 'src/backend/utils/adt/meson.build' cleanly.
Applied patch to 'src/backend/utils/adt/ruleutils.c' cleanly.
Applied patch to 'src/include/catalog/pg_proc.dat' cleanly.
Applied patch to 'src/test/regress/expected/database.out' cleanly.
Applied patch to 'src/test/regress/sql/database.sql' cleanly.
Applied patch to 'src/tools/pgindent/typedefs.list' cleanly.
U doc/src/sgml/func/func-info.sgml
U src/backend/utils/adt/ddlutils.c
diff --cc doc/src/sgml/func/func-info.sgml
index 69ef3857cfa,6915408ae30..00000000000
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@@ -3852,11 -3849,9 +3852,17 @@@ acl | {postgres=arwdDxtm/postgres,
Get Object DDL Functions
++<<<<<<< ours
+ The functions shown in
+ reconstruct DDL statements for various global database objects.
+ Each function returns a set of text rows, one SQL statement per row.
+ (This is a decompiled reconstruction, not the original text of the
+ command.)
++=======
+ The functions described in
+ return the Data Definition Language (DDL) statement for any given database object.
+ This feature is implemented as a set of distinct functions, one for each object type.
++>>>>>>> theirs
@@@ -3880,90 -3875,65 +3886,150 @@@
pg_get_database_ddlpg_get_database_ddl
++<<<<<<< ours
+ ( databaseregdatabase
+ , prettyboolean
+ DEFAULT false
+ , ownerboolean
+ DEFAULT true
+ , tablespaceboolean
+ DEFAULT true )
+ setof text
+
+
+ Reconstructs the CREATE
+ DATABASE statement for the specified database,
+ followed by ALTER DATABASE
+ statements for connection limit, template status,
+ and configuration settings. Each statement is returned as a separate
+ row. When pretty is true, the output is
+ pretty-printed. When owner is false, the
+ OWNER clause is omitted. When
+ tablespace is false, the
+ TABLESPACE clause is omitted.
+
+
+
+
+
+ pg_get_role_ddl
+
+ pg_get_role_ddl
+ ( roleregrole
+ , prettyboolean
+ DEFAULT false
+ , membershipsboolean
+ DEFAULT true )
+ setof text
+
+
+ Reconstructs the CREATE ROLE
+ statement and any
+ ALTER ROLE ... SET statements for the given
+ role. Each statement is returned as a separate row.
+ Password information is never included in the output.
+ When pretty is true, the output is
+ pretty-printed. When memberships is false,
+ GRANT statements
+ for role memberships are omitted.
+
+
+
+
+
+ pg_get_tablespace_ddl
+
+ pg_get_tablespace_ddl
+ ( tablespaceoid
+ , prettyboolean
+ DEFAULT false
+ , ownerboolean
+ DEFAULT true )
+ setof text
+
+
+ pg_get_tablespace_ddl
+ ( tablespacename
+ , prettyboolean
+ DEFAULT false
+ , ownerboolean
+ DEFAULT true )
+ setof text
+
+
+ Reconstructs the CREATE
+ TABLESPACE statement for the specified tablespace (by
+ OID or name). If the tablespace has options set, an
+ ALTER TABLESPACE ... SET
+ statement is also returned. Each statement is
+ returned as a separate row. When pretty is
+ true, the output is pretty-printed. When owner
+ is false, the OWNER clause is omitted.
+
++=======
+ ( database_idregdatabase
+ , VARIADICoptions
+ "any" )
+ text
+
+
+ Reconstructs the CREATE DATABASE statement for the
+ specified database (identified by name or OID) from the system
+ catalogs. The optional variadic arguments are name/value pairs that
+ control the output
+ formatting and content (e.g., 'pretty', true, 'owner', false).
+ Supported options are explained below.
+
++>>>>>>> theirs
++<<<<<<< ours
++=======
+
+ The options for pg_get_database_ddl
+ provide fine-grained control over the generated SQL. Options are passed as
+ alternating key/value pairs where the key is a text string and the
+ value is either a boolean or a text string representing a boolean
+ (true, false, yes,
+ no, 1, 0,
+ on, off):
+
+
+
+ 'pretty', true (or 'pretty', 'yes'):
+ Formats the output with newlines and indentation for better readability.
+ This option defaults to false.
+
+
+
+
+ 'owner', false (or 'owner', 'no'):
+ Omits the OWNER clause from the reconstructed statement.
+ This option defaults to true.
+
+
+
+
+ 'tablespace', false (or 'tablespace', '0'):
+ Omits the TABLESPACE clause from the reconstructed statement.
+ This option defaults to true.
+
+
+
+
+ 'defaults', true (or 'defaults', '1'):
+ Includes clauses for parameters that are currently at their default values
+ (e.g., CONNECTION LIMIT -1), which are normally omitted for brevity.
+ This option defaults to false.
+
+
+
+
+
++>>>>>>> theirs
diff --cc src/backend/utils/adt/ddlutils.c
index a70f1c28655,6e88b8a1a92..00000000000
--- a/src/backend/utils/adt/ddlutils.c
+++ b/src/backend/utils/adt/ddlutils.c
@@@ -1,16 -1,15 +1,27 @@@
/*-------------------------------------------------------------------------
*
* ddlutils.c
++<<<<<<< ours
+ * 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.
++=======
+ * Functions to reconstruct DDL statements from catalog data.
+ *
+ * Unlike ruleutils.c (which deparses expressions and query trees),
+ * these functions generate DDL by reading catalog attributes directly.
++>>>>>>> theirs
*
* Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
++<<<<<<< ours
++=======
+ *
++>>>>>>> theirs
* IDENTIFICATION
* src/backend/utils/adt/ddlutils.c
*
@@@ -18,64 -17,90 +29,146 @@@
*/
#include "postgres.h"
++<<<<<<< ours
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "catalog/pg_auth_members.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_database.h"
+#include "catalog/pg_db_role_setting.h"
+#include "catalog/pg_tablespace.h"
+#include "commands/tablespace.h"
+#include "common/relpath.h"
++=======
+ #include
+
+ #include "access/htup_details.h"
+ #include "catalog/pg_authid.h"
+ #include "catalog/pg_collation.h"
+ #include "catalog/pg_database.h"
+ #include "catalog/pg_tablespace.h"
+ #include "commands/tablespace.h"
++>>>>>>> theirs
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
++<<<<<<< ours
+#include "utils/datetime.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
+#include "utils/varlena.h"
+
+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);
+static List *pg_get_role_ddl_internal(Oid roleid, bool pretty,
+ bool memberships);
+static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner);
+static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid);
+static List *pg_get_database_ddl_internal(Oid dbid, bool pretty,
+ bool no_owner, bool no_tablespace);
+
+
+/*
+ * 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);
++=======
+ #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
+
+ /* Pretty flags (subset needed for DDL formatting) */
+ #define PRETTYFLAG_INDENT 0x0002
+
+ /* DDL Options flags */
+ #define PG_DDL_PRETTY_INDENT 0x00000001
+ #define PG_DDL_WITH_DEFAULTS 0x00000002
+ #define PG_DDL_NO_OWNER 0x00000004
+ #define PG_DDL_NO_TABLESPACE 0x00000008
+
+ /*
+ * Structure to define DDL options for parse_ddl_options().
+ * This allows easy addition of new options in the future.
+ */
+ typedef struct DDLOptionDef
+ {
+ const char *name; /* Option name (case-insensitive) */
+ uint32 flag; /* Flag to set */
+ bool set_on_true; /* If true, set flag when value is true; if
+ * false, set flag when value is false */
+ } DDLOptionDef;
+
+ /*
+ * Array of supported DDL options.
+ * To add a new option, simply add an entry to this array.
+ */
+ static const DDLOptionDef ddl_option_defs[] = {
+ {"pretty", PG_DDL_PRETTY_INDENT, true},
+ {"defaults", PG_DDL_WITH_DEFAULTS, true},
+ {"owner", PG_DDL_NO_OWNER, false},
+ {"tablespace", PG_DDL_NO_TABLESPACE, false},
+ };
+
+ #define GET_DDL_PRETTY_FLAGS(pretty) \
+ ((pretty) ? (PRETTYFLAG_INDENT) \
+ : 0)
+
+ /* Local function declarations */
+ static char *pg_get_database_ddl_worker(Oid db_oid, uint32 ddl_flags);
+ static void get_formatted_string(StringInfo buf, int prettyFlags, int nSpaces, const char *fmt,...) pg_attribute_printf(4, 5);
+ static uint32 parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start);
+
+ /*
+ * get_formatted_string
+ *
+ * Helper function to append formatted strings to a StringInfo buffer, with
+ * optional pretty-printing based on flags.
+ *
+ * prettyFlags - Based on prettyFlags the output includes spaces and
+ * newlines (\n).
+ * nSpaces - indent with specified number of space characters.
+ * fmt - printf-style format string used by appendStringInfoVA.
+ */
+ static void
+ get_formatted_string(StringInfo buf, int prettyFlags, int nSpaces, const char *fmt,...)
+ {
+ va_list args;
+
+ if (prettyFlags & PRETTYFLAG_INDENT)
+ {
+ appendStringInfoChar(buf, '\n');
+ /* Indent with spaces */
+ appendStringInfoSpaces(buf, nSpaces);
++>>>>>>> theirs
}
else
appendStringInfoChar(buf, ' ');
for (;;)
{
++<<<<<<< ours
+ va_list args;
++=======
++>>>>>>> theirs
int needed;
va_start(args, fmt);
@@@ -88,890 -113,374 +181,1263 @@@
}
/*
++<<<<<<< ours
+ * 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);
+}
+
+/*
+ * pg_get_role_ddl_internal
+ * Generate DDL statements to recreate a role
+ *
+ * Returns a List of palloc'd strings, each being a complete SQL statement.
+ * The first list element is always the CREATE ROLE statement; subsequent
+ * elements are ALTER ROLE SET statements for any role-specific or
+ * role-in-database configuration settings. If memberships is true,
+ * GRANT statements for role memberships are appended.
+ */
+static List *
+pg_get_role_ddl_internal(Oid roleid, bool pretty, bool memberships)
+{
+ HeapTuple tuple;
+ Form_pg_authid roleform;
+ StringInfoData buf;
+ char *rolname;
+ Datum rolevaliduntil;
+ bool isnull;
+ Relation rel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ List *statements = NIL;
+
+ tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role with OID %u does not exist", roleid)));
+
+ roleform = (Form_pg_authid) GETSTRUCT(tuple);
+ rolname = pstrdup(NameStr(roleform->rolname));
+
+ /* User must have SELECT privilege on pg_authid. */
+ if (pg_class_aclcheck(AuthIdRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ {
+ ReleaseSysCache(tuple);
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for role %s", rolname)));
+ }
+
+ /*
+ * We don't support generating DDL for system roles. The primary reason
+ * for this is that users shouldn't be recreating them.
+ */
+ if (IsReservedName(rolname))
+ ereport(ERROR,
+ (errcode(ERRCODE_RESERVED_NAME),
+ errmsg("role name \"%s\" is reserved", rolname),
+ errdetail("Role names starting with \"pg_\" are reserved for system roles.")));
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "CREATE ROLE %s", quote_identifier(rolname));
+
+ /*
+ * Append role attributes. The order here follows the same sequence as
+ * you'd typically write them in a CREATE ROLE command, though any order
+ * is actually acceptable to the parser.
+ */
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolsuper ? "SUPERUSER" : "NOSUPERUSER");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolinherit ? "INHERIT" : "NOINHERIT");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcreaterole ? "CREATEROLE" : "NOCREATEROLE");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcreatedb ? "CREATEDB" : "NOCREATEDB");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcanlogin ? "LOGIN" : "NOLOGIN");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolreplication ? "REPLICATION" : "NOREPLICATION");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolbypassrls ? "BYPASSRLS" : "NOBYPASSRLS");
+
+ /*
+ * CONNECTION LIMIT is only interesting if it's not -1 (the default,
+ * meaning no limit).
+ */
+ if (roleform->rolconnlimit >= 0)
+ append_ddl_option(&buf, pretty, 4, "CONNECTION LIMIT %d",
+ roleform->rolconnlimit);
+
+ rolevaliduntil = SysCacheGetAttr(AUTHOID, tuple,
+ Anum_pg_authid_rolvaliduntil,
+ &isnull);
+ if (!isnull)
+ {
+ TimestampTz ts;
+ int tz;
+ struct pg_tm tm;
+ fsec_t fsec;
+ const char *tzn;
+ char ts_str[MAXDATELEN + 1];
+
+ ts = DatumGetTimestampTz(rolevaliduntil);
+ if (TIMESTAMP_NOT_FINITE(ts))
+ EncodeSpecialTimestamp(ts, ts_str);
+ else if (timestamp2tm(ts, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ EncodeDateTime(&tm, fsec, true, tz, tzn, USE_ISO_DATES, ts_str);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ append_ddl_option(&buf, pretty, 4, "VALID UNTIL %s",
+ quote_literal_cstr(ts_str));
+ }
+
+ ReleaseSysCache(tuple);
+
+ /*
+ * We intentionally omit PASSWORD. There's no way to retrieve the
+ * original password text from the stored hash, and even if we could,
+ * exposing passwords through a SQL function would be a security issue.
+ * Users must set passwords separately after recreating roles.
+ */
+
+ appendStringInfoChar(&buf, ';');
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ /*
+ * Now scan pg_db_role_setting for ALTER ROLE SET configurations.
+ *
+ * These can be role-wide (setdatabase = 0) or specific to a particular
+ * database (setdatabase = a valid DB OID). It generates one ALTER
+ * statement per setting.
+ */
+ rel = table_open(DbRoleSettingRelationId, AccessShareLock);
+ ScanKeyInit(&scankey,
+ Anum_pg_db_role_setting_setrole,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+ scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
+ NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_db_role_setting setting = (Form_pg_db_role_setting) GETSTRUCT(tuple);
+ Oid datid = setting->setdatabase;
+ Datum datum;
+ ArrayType *role_settings;
+ Datum *settings;
+ bool *nulls;
+ int nsettings;
+ char *datname = NULL;
+
+ /*
+ * If setdatabase is valid, this is a role-in-database setting;
+ * otherwise it's a role-wide setting. Look up the database name once
+ * for all settings in this row.
+ */
+ if (OidIsValid(datid))
+ {
+ datname = get_database_name(datid);
+ /* Database has been dropped; skip all settings in this row. */
+ if (datname == NULL)
+ continue;
+ }
+
+ /*
+ * The setconfig column is a text array in "name=value" format. It
+ * should never be null for a valid row, but be defensive.
+ */
+ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ continue;
+
+ role_settings = DatumGetArrayTypePCopy(datum);
+
+ deconstruct_array_builtin(role_settings, TEXTOID, &settings, &nulls, &nsettings);
+
+ for (int i = 0; i < nsettings; i++)
+ {
+ char *s,
+ *p;
+
+ if (nulls[i])
+ continue;
+
+ s = TextDatumGetCString(settings[i]);
+ p = strchr(s, '=');
+ if (p == NULL)
+ {
+ pfree(s);
+ continue;
+ }
+ *p++ = '\0';
+
+ /* Build a fresh ALTER ROLE statement for this setting */
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER ROLE %s", quote_identifier(rolname));
+
+ if (datname != NULL)
+ appendStringInfo(&buf, " IN DATABASE %s",
+ quote_identifier(datname));
+
+ appendStringInfo(&buf, " SET %s TO ",
+ quote_identifier(s));
+
+ append_guc_value(&buf, s, p);
+
+ appendStringInfoChar(&buf, ';');
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ pfree(s);
+ }
+
+ pfree(settings);
+ pfree(nulls);
+ pfree(role_settings);
+
+ if (datname != NULL)
+ pfree(datname);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ /*
+ * Scan pg_auth_members for role memberships. We look for rows where
+ * member = roleid, meaning this role has been granted membership in other
+ * roles.
+ */
+ if (memberships)
+ {
+ rel = table_open(AuthMemRelationId, AccessShareLock);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_members_member,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+ scan = systable_beginscan(rel, AuthMemMemRoleIndexId, true,
+ NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple);
+ char *granted_role;
+ char *grantor;
+
+ granted_role = GetUserNameFromId(memform->roleid, false);
+ grantor = GetUserNameFromId(memform->grantor, false);
+
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "GRANT %s TO %s",
+ quote_identifier(granted_role),
+ quote_identifier(rolname));
+ appendStringInfo(&buf, " WITH ADMIN %s, INHERIT %s, SET %s",
+ memform->admin_option ? "TRUE" : "FALSE",
+ memform->inherit_option ? "TRUE" : "FALSE",
+ memform->set_option ? "TRUE" : "FALSE");
+ appendStringInfo(&buf, " GRANTED BY %s;",
+ quote_identifier(grantor));
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ pfree(granted_role);
+ pfree(grantor);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+ }
+
+ pfree(buf.data);
+ pfree(rolname);
+
+ return statements;
+}
+
+/*
+ * pg_get_role_ddl
+ * Return DDL to recreate a role as a set of text rows.
+ *
+ * Each row is a complete SQL statement. The first row is always the
+ * CREATE ROLE statement; subsequent rows are ALTER ROLE SET statements
+ * and optionally GRANT statements for role memberships.
+ */
+Datum
+pg_get_role_ddl(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *statements;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ Oid roleid;
+ bool pretty;
+ bool memberships;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ roleid = PG_GETARG_OID(0);
+ pretty = PG_GETARG_BOOL(1);
+ memberships = PG_GETARG_BOOL(2);
+
+ statements = pg_get_role_ddl_internal(roleid, pretty, memberships);
+ funcctx->user_fctx = statements;
+ funcctx->max_calls = list_length(statements);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ statements = (List *) funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ char *stmt;
+
+ stmt = list_nth(statements, funcctx->call_cntr);
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
+ }
+ else
+ {
+ list_free_deep(statements);
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
+/*
+ * pg_get_tablespace_ddl_internal
+ * Generate DDL statements to recreate a tablespace.
+ *
+ * Returns a List of palloc'd strings. The first element is the
+ * CREATE TABLESPACE statement; if the tablespace has reloptions,
+ * a second element with ALTER TABLESPACE SET (...) is appended.
+ */
+static List *
+pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner)
+{
+ HeapTuple tuple;
+ Form_pg_tablespace tspForm;
+ StringInfoData buf;
+ char *spcname;
+ char *spcowner;
+ char *path;
+ bool isNull;
+ Datum datum;
+ List *statements = NIL;
+
+ tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tsid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tablespace with OID %u does not exist",
+ tsid)));
+
+ tspForm = (Form_pg_tablespace) GETSTRUCT(tuple);
+ spcname = pstrdup(NameStr(tspForm->spcname));
+
+ /* User must have SELECT privilege on pg_tablespace. */
+ if (pg_class_aclcheck(TableSpaceRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ {
+ ReleaseSysCache(tuple);
+ aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE, spcname);
+ }
+
+ /*
+ * We don't support generating DDL for system tablespaces. The primary
+ * reason for this is that users shouldn't be recreating them.
+ */
+ if (IsReservedName(spcname))
+ ereport(ERROR,
+ (errcode(ERRCODE_RESERVED_NAME),
+ errmsg("tablespace name \"%s\" is reserved", spcname),
+ errdetail("Tablespace names starting with \"pg_\" are reserved for system tablespaces.")));
+
+ initStringInfo(&buf);
+
+ /* Start building the CREATE TABLESPACE statement */
+ appendStringInfo(&buf, "CREATE TABLESPACE %s", quote_identifier(spcname));
+
+ /* Add OWNER clause */
+ if (!no_owner)
+ {
+ spcowner = GetUserNameFromId(tspForm->spcowner, false);
+ append_ddl_option(&buf, pretty, 4, "OWNER %s",
+ quote_identifier(spcowner));
+ pfree(spcowner);
+ }
+
+ /* Find tablespace directory path */
+ path = get_tablespace_location(tsid);
+
+ /* Add directory LOCATION (path), if it exists */
+ if (path[0] != '\0')
+ {
+ /*
+ * Special case: if the tablespace was created with GUC
+ * "allow_in_place_tablespaces = true" and "LOCATION ''", path will
+ * begin with "pg_tblspc/". In that case, show "LOCATION ''" as the
+ * user originally specified.
+ */
+ if (strncmp(PG_TBLSPC_DIR_SLASH, path, strlen(PG_TBLSPC_DIR_SLASH)) == 0)
+ append_ddl_option(&buf, pretty, 4, "LOCATION ''");
+ else
+ append_ddl_option(&buf, pretty, 4, "LOCATION %s",
+ quote_literal_cstr(path));
+ }
+ pfree(path);
+
+ appendStringInfoChar(&buf, ';');
+ statements = lappend(statements, pstrdup(buf.data));
+
+ /* Check for tablespace options */
+ datum = SysCacheGetAttr(TABLESPACEOID, tuple,
+ Anum_pg_tablespace_spcoptions, &isNull);
+ if (!isNull)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER TABLESPACE %s SET (",
+ quote_identifier(spcname));
+ get_reloptions(&buf, datum);
+ appendStringInfoString(&buf, ");");
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ ReleaseSysCache(tuple);
+ pfree(spcname);
+ pfree(buf.data);
+
+ return statements;
+}
+
+/*
+ * pg_get_tablespace_ddl_srf - common SRF logic for tablespace DDL
+ */
+static Datum
+pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid)
+{
+ FuncCallContext *funcctx;
+ List *statements;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ bool pretty;
+ bool no_owner;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ pretty = PG_GETARG_BOOL(1);
+ no_owner = !PG_GETARG_BOOL(2);
+
+ statements = pg_get_tablespace_ddl_internal(tsid, pretty, no_owner);
+ funcctx->user_fctx = statements;
+ funcctx->max_calls = list_length(statements);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ statements = (List *) funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ char *stmt;
+
+ stmt = (char *) list_nth(statements, funcctx->call_cntr);
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
+ }
+ else
+ {
+ list_free_deep(statements);
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
+/*
+ * pg_get_tablespace_ddl_oid
+ * Return DDL to recreate a tablespace, taking OID.
+ */
+Datum
+pg_get_tablespace_ddl_oid(PG_FUNCTION_ARGS)
+{
+ Oid tsid = PG_GETARG_OID(0);
+
+ return pg_get_tablespace_ddl_srf(fcinfo, tsid);
+}
+
+/*
+ * pg_get_tablespace_ddl_name
+ * Return DDL to recreate a tablespace, taking name.
+ */
+Datum
+pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS)
+{
+ Name tspname = PG_GETARG_NAME(0);
+ Oid tsid = get_tablespace_oid(NameStr(*tspname), false);
+
+ return pg_get_tablespace_ddl_srf(fcinfo, tsid);
+}
+
+/*
+ * pg_get_database_ddl_internal
+ * Generate DDL statements to recreate a database.
+ *
+ * Returns a List of palloc'd strings. The first element is the
+ * CREATE DATABASE statement; subsequent elements are ALTER DATABASE
+ * statements for properties and configuration settings.
+ */
+static List *
+pg_get_database_ddl_internal(Oid dbid, bool pretty,
+ bool no_owner, bool no_tablespace)
+{
+ HeapTuple tuple;
+ Form_pg_database dbform;
+ StringInfoData buf;
+ bool isnull;
+ Datum datum;
+ const char *encoding;
+ char *dbname;
+ char *collate;
+ char *ctype;
+ Relation rel;
+ ScanKeyData scankey[2];
+ SysScanDesc scan;
+ List *statements = NIL;
+ AclResult aclresult;
+
+ tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("database with OID %u does not exist", dbid)));
+
+ /* User must have connect privilege for target database. */
+ aclresult = object_aclcheck(DatabaseRelationId, dbid, GetUserId(), ACL_CONNECT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_DATABASE,
+ get_database_name(dbid));
+
+ dbform = (Form_pg_database) GETSTRUCT(tuple);
+ dbname = pstrdup(NameStr(dbform->datname));
+
+ /*
+ * Reject invalid databases. Deparsing a pg_database row in invalid state
+ * can produce SQL that is not executable, such as CONNECTION LIMIT = -2.
+ */
+ if (database_is_invalid_form(dbform))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot generate DDL for invalid database \"%s\"",
+ dbname)));
+
+ /*
+ * We don't support generating DDL for system databases. The primary
+ * reason for this is that users shouldn't be recreating them.
+ */
+ if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_RESERVED_NAME),
+ errmsg("database \"%s\" is a system database", dbname),
+ errdetail("DDL generation is not supported for template0 and template1.")));
+
+ initStringInfo(&buf);
+
+ /* --- Build CREATE DATABASE statement --- */
+ appendStringInfo(&buf, "CREATE DATABASE %s", quote_identifier(dbname));
+
+ /*
+ * Always use template0: the target database already contains the catalog
+ * data from whatever template was used originally, so we must start from
+ * the pristine template to avoid duplication.
+ */
+ append_ddl_option(&buf, pretty, 4, "WITH TEMPLATE = template0");
+
+ /* ENCODING */
+ encoding = pg_encoding_to_char(dbform->encoding);
+ if (strlen(encoding) > 0)
+ append_ddl_option(&buf, pretty, 4, "ENCODING = %s",
+ quote_literal_cstr(encoding));
+
+ /* LOCALE_PROVIDER */
+ if (dbform->datlocprovider == COLLPROVIDER_BUILTIN ||
+ dbform->datlocprovider == COLLPROVIDER_ICU ||
+ dbform->datlocprovider == COLLPROVIDER_LIBC)
+ append_ddl_option(&buf, pretty, 4, "LOCALE_PROVIDER = %s",
+ collprovider_name(dbform->datlocprovider));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("unrecognized locale provider: %c",
+ dbform->datlocprovider)));
+
+ /* LOCALE, LC_COLLATE, LC_CTYPE */
+ datum = SysCacheGetAttr(DATABASEOID, tuple,
+ Anum_pg_database_datcollate, &isnull);
+ collate = isnull ? NULL : TextDatumGetCString(datum);
+ datum = SysCacheGetAttr(DATABASEOID, tuple,
+ Anum_pg_database_datctype, &isnull);
+ ctype = isnull ? NULL : TextDatumGetCString(datum);
+ if (collate != NULL && ctype != NULL && strcmp(collate, ctype) == 0)
+ {
+ append_ddl_option(&buf, pretty, 4, "LOCALE = %s",
+ quote_literal_cstr(collate));
+ }
+ else
+ {
+ if (collate != NULL)
+ append_ddl_option(&buf, pretty, 4, "LC_COLLATE = %s",
+ quote_literal_cstr(collate));
+ if (ctype != NULL)
+ append_ddl_option(&buf, pretty, 4, "LC_CTYPE = %s",
+ quote_literal_cstr(ctype));
+ }
+
+ /* LOCALE (provider-specific) */
+ datum = SysCacheGetAttr(DATABASEOID, tuple,
+ Anum_pg_database_datlocale, &isnull);
+ if (!isnull)
+ {
+ const char *locale = TextDatumGetCString(datum);
+
+ if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
+ append_ddl_option(&buf, pretty, 4, "BUILTIN_LOCALE = %s",
+ quote_literal_cstr(locale));
+ else if (dbform->datlocprovider == COLLPROVIDER_ICU)
+ append_ddl_option(&buf, pretty, 4, "ICU_LOCALE = %s",
+ quote_literal_cstr(locale));
+ }
+
+ /* ICU_RULES */
+ datum = SysCacheGetAttr(DATABASEOID, tuple,
+ Anum_pg_database_daticurules, &isnull);
+ if (!isnull && dbform->datlocprovider == COLLPROVIDER_ICU)
+ append_ddl_option(&buf, pretty, 4, "ICU_RULES = %s",
+ quote_literal_cstr(TextDatumGetCString(datum)));
+
+ /* TABLESPACE */
+ if (!no_tablespace && OidIsValid(dbform->dattablespace))
+ {
+ char *spcname = get_tablespace_name(dbform->dattablespace);
+
+ if (spcname == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("tablespace with OID %u does not exist",
+ dbform->dattablespace),
+ errdetail("It may have been concurrently dropped.")));
+
+ if (pg_strcasecmp(spcname, "pg_default") != 0)
+ append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s",
+ quote_identifier(spcname));
+ }
+
+ appendStringInfoChar(&buf, ';');
+ statements = lappend(statements, pstrdup(buf.data));
+
+ /* OWNER */
+ if (!no_owner && OidIsValid(dbform->datdba))
+ {
+ char *owner = GetUserNameFromId(dbform->datdba, false);
+
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER DATABASE %s OWNER TO %s;",
+ quote_identifier(dbname), quote_identifier(owner));
+ pfree(owner);
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ /* CONNECTION LIMIT */
+ if (dbform->datconnlimit != -1)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER DATABASE %s CONNECTION LIMIT = %d;",
+ quote_identifier(dbname), dbform->datconnlimit);
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ /* IS_TEMPLATE */
+ if (dbform->datistemplate)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER DATABASE %s IS_TEMPLATE = true;",
+ quote_identifier(dbname));
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ /* ALLOW_CONNECTIONS */
+ if (!dbform->datallowconn)
+ {
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER DATABASE %s ALLOW_CONNECTIONS = false;",
+ quote_identifier(dbname));
+ statements = lappend(statements, pstrdup(buf.data));
+ }
+
+ ReleaseSysCache(tuple);
+
+ /*
+ * Now scan pg_db_role_setting for ALTER DATABASE SET configurations.
+ *
+ * It is only database-wide (setrole = 0). It generates one ALTER
+ * statement per setting.
+ */
+ rel = table_open(DbRoleSettingRelationId, AccessShareLock);
+ ScanKeyInit(&scankey[0],
+ Anum_pg_db_role_setting_setdatabase,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(dbid));
+ ScanKeyInit(&scankey[1],
+ Anum_pg_db_role_setting_setrole,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(InvalidOid));
+
+ scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
+ NULL, 2, scankey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ ArrayType *dbconfig;
+ Datum *settings;
+ bool *nulls;
+ int nsettings;
+
+ /*
+ * The setconfig column is a text array in "name=value" format. It
+ * should never be null for a valid row, but be defensive.
+ */
+ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ continue;
+
+ dbconfig = DatumGetArrayTypePCopy(datum);
+
+ deconstruct_array_builtin(dbconfig, TEXTOID, &settings, &nulls, &nsettings);
+
+ for (int i = 0; i < nsettings; i++)
+ {
+ char *s,
+ *p;
+
+ if (nulls[i])
+ continue;
+
+ s = TextDatumGetCString(settings[i]);
+ p = strchr(s, '=');
+ if (p == NULL)
+ {
+ pfree(s);
+ continue;
+ }
+ *p++ = '\0';
+
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER DATABASE %s SET %s TO ",
+ quote_identifier(dbname),
+ quote_identifier(s));
+
+ append_guc_value(&buf, s, p);
+
+ appendStringInfoChar(&buf, ';');
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ pfree(s);
+ }
+
+ pfree(settings);
+ pfree(nulls);
+ pfree(dbconfig);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ pfree(buf.data);
+ pfree(dbname);
+
+ return statements;
+}
+
+/*
+ * pg_get_database_ddl
+ * Return DDL to recreate a database as a set of text rows.
+ */
+Datum
+pg_get_database_ddl(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *statements;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ Oid dbid;
+ bool pretty;
+ bool no_owner;
+ bool no_tablespace;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ dbid = PG_GETARG_OID(0);
+ pretty = PG_GETARG_BOOL(1);
+ no_owner = !PG_GETARG_BOOL(2);
+ no_tablespace = !PG_GETARG_BOOL(3);
+
+ statements = pg_get_database_ddl_internal(dbid, pretty, no_owner,
+ no_tablespace);
+ funcctx->user_fctx = statements;
+ funcctx->max_calls = list_length(statements);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ statements = (List *) funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ char *stmt;
+
+ stmt = list_nth(statements, funcctx->call_cntr);
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
+ }
+ else
+ {
+ list_free_deep(statements);
+ SRF_RETURN_DONE(funcctx);
+ }
++=======
+ * parse_ddl_options - Generic helper to parse variadic name/value options
+ * fcinfo: The FunctionCallInfo from the calling function
+ * variadic_start: The argument position where variadic arguments start
+ *
+ * Returns: Bitmask of flags based on the parsed options.
+ *
+ * Options are passed as name/value pairs.
+ * For example: pg_get_database_ddl('mydb', 'owner', false, 'pretty', true)
+ */
+ static uint32
+ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
+ {
+ uint32 flags = 0;
+ uint32 seen_flags = 0;
+ Datum *args;
+ bool *nulls;
+ Oid *types;
+ int nargs;
+
+ /* Extract variadic arguments */
+ nargs = extract_variadic_args(fcinfo, variadic_start, true,
+ &args, &types, &nulls);
+
+ /* If no options provided (VARIADIC NULL), return the empty bitmask */
+ if (nargs <= 0)
+ return flags;
+
+ /*
+ * Handle the case where DEFAULT NULL was used and no explicit variadic
+ * arguments were provided. In this case, we get a single NULL argument.
+ */
+ if (nargs == 1 && nulls[0])
+ return flags;
+
+ /* Arguments must come in name/value pairs */
+ 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 (int i = 0; i < nargs; i += 2)
+ {
+ char *name;
+ bool bval;
+ bool found = false;
+
+ /* Key must not be null */
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("name at variadic position %d is null", i + 1)));
+
+ /* Key must be text type */
+ if (types[i] != TEXTOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("name at variadic position %d has type %s, expected type %s",
+ i + 1, format_type_be(types[i]),
+ format_type_be(TEXTOID))));
+
+ name = TextDatumGetCString(args[i]);
+
+ /* Value must not be null */
+ if (nulls[i + 1])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for option \"%s\" must not be null",
+ name)));
+
+ /* Value must be boolean or text type */
+ if (types[i + 1] == BOOLOID)
+ {
+ bval = DatumGetBool(args[i + 1]);
+ }
+ else if (types[i + 1] == TEXTOID)
+ {
+ char *valstr = TextDatumGetCString(args[i + 1]);
+
+ if (!parse_bool(valstr, &bval))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for option \"%s\" at position %d has invalid value \"%s\"",
+ name, i + 2, valstr),
+ errhint("Valid values are: true, false, yes, no, 1, 0, on, off.")));
+ pfree(valstr);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value for option \"%s\" at position %d has type %s, expected type boolean or text",
+ name, i + 2, format_type_be(types[i + 1]))));
+ }
+
+ /*
+ * Look up the option in the ddl_option_defs array and set the
+ * appropriate flag based on the value.
+ */
+ for (int j = 0; j < lengthof(ddl_option_defs); j++)
+ {
+ const DDLOptionDef *opt = &ddl_option_defs[j];
+
+ if (pg_strcasecmp(name, opt->name) == 0)
+ {
+ /* Error if this option was already specified */
+ if (seen_flags & opt->flag)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("option \"%s\" is specified more than once", name)));
+
+ seen_flags |= opt->flag;
+
+ /*
+ * Set the flag if the value matches the set_on_true
+ * condition: if set_on_true is true, set flag when bval is
+ * true; if set_on_true is false, set flag when bval is false.
+ */
+ if (bval == opt->set_on_true)
+ flags |= opt->flag;
+
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized option: \"%s\"", name)));
+
+ pfree(name);
+ }
+
+ return flags;
+ }
+
+ /*
+ * pg_get_database_ddl
+ *
+ * Generate a CREATE DATABASE statement for the specified database oid.
+ *
+ * db_oid - OID of the database for which to generate the DDL.
+ * options - Variadic name/value pairs to modify the output.
+ */
+ Datum
+ pg_get_database_ddl(PG_FUNCTION_ARGS)
+ {
+ Oid db_oid;
+ uint32 ddl_flags;
+ char *res;
+
+ if (PG_ARGISNULL(0))
+ PG_RETURN_NULL();
+
+ db_oid = PG_GETARG_OID(0);
+
+ /* Parse variadic options starting from argument 1 */
+ ddl_flags = parse_ddl_options(fcinfo, 1);
+
+ res = pg_get_database_ddl_worker(db_oid, ddl_flags);
+
+ PG_RETURN_TEXT_P(cstring_to_text(res));
+ }
+
+ static char *
+ pg_get_database_ddl_worker(Oid db_oid, uint32 ddl_flags)
+ {
+ const char *encoding;
+ bool attr_isnull;
+ Datum dbvalue;
+ HeapTuple tuple_database;
+ Form_pg_database dbform;
+ StringInfoData buf;
+ StringInfoData optbuf;
+ AclResult aclresult;
+ HeapTuple tmpl_tuple;
+ int tmpl_encoding = -1;
+ char *collate;
+ char *ctype;
+
+ /* Variables for ddl_options parsing */
+ int pretty_flags = 0;
+ bool is_with_defaults = false;
+
+ /* Set the appropriate flags */
+ if (ddl_flags & PG_DDL_PRETTY_INDENT)
+ pretty_flags = GET_DDL_PRETTY_FLAGS(1);
+
+ is_with_defaults = (ddl_flags & PG_DDL_WITH_DEFAULTS) != 0;
+
+ /*
+ * User must have connect privilege for target database.
+ */
+ aclresult = object_aclcheck(DatabaseRelationId, db_oid, GetUserId(),
+ ACL_CONNECT);
+ if (aclresult != ACLCHECK_OK &&
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ {
+ aclcheck_error(aclresult, OBJECT_DATABASE,
+ get_database_name(db_oid));
+ }
+
+ /* Look up the database in pg_database */
+ tuple_database = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(db_oid));
+ if (!HeapTupleIsValid(tuple_database))
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("database with oid %u does not exist", db_oid));
+
+ dbform = (Form_pg_database) GETSTRUCT(tuple_database);
+
+ initStringInfo(&buf);
+ initStringInfo(&optbuf);
+
+ /*
+ * Build the options into a separate buffer first, so we can emit WITH
+ * only when there are options to show.
+ */
+
+ /* Set the OWNER in the DDL if owner is not omitted */
+ if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
+ {
+ char *dbowner = GetUserNameFromId(dbform->datdba, false);
+
+ get_formatted_string(&optbuf, pretty_flags, 8, "OWNER = %s",
+ quote_identifier(dbowner));
+ }
+
+ /*
+ * Emit ENCODING if it differs from template1's encoding, or if defaults
+ * are requested. The default encoding for CREATE DATABASE comes from the
+ * template database (template1), not a fixed value.
+ */
+ encoding = pg_encoding_to_char(dbform->encoding);
+
+ tmpl_tuple = SearchSysCache1(DATABASEOID,
+ ObjectIdGetDatum(Template1DbOid));
+ if (HeapTupleIsValid(tmpl_tuple))
+ {
+ Form_pg_database tmplform = (Form_pg_database) GETSTRUCT(tmpl_tuple);
+
+ tmpl_encoding = tmplform->encoding;
+ ReleaseSysCache(tmpl_tuple);
+ }
+
+ if (is_with_defaults || dbform->encoding != tmpl_encoding)
+ get_formatted_string(&optbuf, pretty_flags, 8, "ENCODING = %s",
+ quote_literal_cstr(encoding));
+
+ /*
+ * LC_COLLATE and LC_CTYPE are BKI_FORCE_NOT_NULL, always present. Emit
+ * LOCALE when they match (like pg_dump), otherwise emit separately.
+ */
+ dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database,
+ Anum_pg_database_datcollate, &attr_isnull);
+ Assert(!attr_isnull);
+ collate = TextDatumGetCString(dbvalue);
+
+ dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database,
+ Anum_pg_database_datctype, &attr_isnull);
+ Assert(!attr_isnull);
+ ctype = TextDatumGetCString(dbvalue);
+
+ if (strcmp(collate, ctype) == 0)
+ {
+ get_formatted_string(&optbuf, pretty_flags, 8, "LOCALE = %s",
+ quote_literal_cstr(collate));
+ }
+ else
+ {
+ get_formatted_string(&optbuf, pretty_flags, 8, "LC_COLLATE = %s",
+ quote_literal_cstr(collate));
+ get_formatted_string(&optbuf, pretty_flags, 8, "LC_CTYPE = %s",
+ quote_literal_cstr(ctype));
+ }
+
+
+ /*
+ * Fetch datlocale: emit as BUILTIN_LOCALE or ICU_LOCALE depending on the
+ * provider. For libc, datlocale should always be NULL.
+ */
+ dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database,
+ Anum_pg_database_datlocale, &attr_isnull);
+ if (!attr_isnull && dbform->datlocprovider == COLLPROVIDER_BUILTIN)
+ get_formatted_string(&optbuf, pretty_flags, 8, "BUILTIN_LOCALE = %s",
+ quote_literal_cstr(TextDatumGetCString(dbvalue)));
+ else if (!attr_isnull && dbform->datlocprovider == COLLPROVIDER_ICU)
+ get_formatted_string(&optbuf, pretty_flags, 8, "ICU_LOCALE = %s",
+ quote_literal_cstr(TextDatumGetCString(dbvalue)));
+ else
+ Assert(attr_isnull);
+
+ /* Fetch the value of ICU_RULES */
+ dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database,
+ Anum_pg_database_daticurules, &attr_isnull);
+ if (!attr_isnull && dbform->datlocprovider == COLLPROVIDER_ICU)
+ get_formatted_string(&optbuf, pretty_flags, 8, "ICU_RULES = %s",
+ quote_literal_cstr(TextDatumGetCString(dbvalue)));
+
+ /*
+ * Emit COLLATION_VERSION only when defaults are requested. Normally this
+ * is an internal implementation detail that should be determined freshly
+ * by the target cluster (similar to how pg_dump only emits it during
+ * binary upgrades).
+ */
+ if (is_with_defaults)
+ {
+ dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database,
+ Anum_pg_database_datcollversion, &attr_isnull);
+ if (!attr_isnull)
+ get_formatted_string(&optbuf, pretty_flags, 8, "COLLATION_VERSION = %s",
+ quote_literal_cstr(TextDatumGetCString(dbvalue)));
+ }
+
+ /* Set the appropriate LOCALE_PROVIDER */
+ if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
+ get_formatted_string(&optbuf, pretty_flags, 8, "LOCALE_PROVIDER = builtin");
+ else if (dbform->datlocprovider == COLLPROVIDER_ICU)
+ get_formatted_string(&optbuf, pretty_flags, 8, "LOCALE_PROVIDER = icu");
+ else
+ get_formatted_string(&optbuf, pretty_flags, 8, "LOCALE_PROVIDER = libc");
+
+ /* Set the TABLESPACE in the DDL if tablespace is not omitted */
+ if (OidIsValid(dbform->dattablespace) && !(ddl_flags & PG_DDL_NO_TABLESPACE))
+ {
+ if (is_with_defaults ||
+ dbform->dattablespace != DEFAULTTABLESPACE_OID)
+ {
+ char *dbTablespace = get_tablespace_name(dbform->dattablespace);
+
+ get_formatted_string(&optbuf, pretty_flags, 8, "TABLESPACE = %s",
+ quote_identifier(dbTablespace));
+ }
+ }
+
+ if (is_with_defaults || !dbform->datallowconn)
+ {
+ get_formatted_string(&optbuf, pretty_flags, 8, "ALLOW_CONNECTIONS = %s",
+ dbform->datallowconn ? "true" : "false");
+ }
+
+ if (is_with_defaults ||
+ dbform->datconnlimit != DATCONNLIMIT_UNLIMITED)
+ {
+ get_formatted_string(&optbuf, pretty_flags, 8, "CONNECTION LIMIT = %d",
+ dbform->datconnlimit);
+ }
+
+ if (is_with_defaults || dbform->datistemplate)
+ get_formatted_string(&optbuf, pretty_flags, 8, "IS_TEMPLATE = %s",
+ dbform->datistemplate ? "true" : "false");
+
+ /* Build the CREATE DATABASE statement */
+ appendStringInfo(&buf, "CREATE DATABASE %s",
+ quote_identifier(dbform->datname.data));
+
+ /* Only emit WITH if there are options */
+ if (optbuf.len > 0)
+ {
+ get_formatted_string(&buf, pretty_flags, 4, "WITH");
+ appendStringInfoString(&buf, optbuf.data);
+ }
+
+ pfree(optbuf.data);
+ appendStringInfoChar(&buf, ';');
+
+ ReleaseSysCache(tuple_database);
+
+ return buf.data;
++>>>>>>> theirs
}