=== 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 + , pretty boolean + DEFAULT false + , owner boolean + DEFAULT true + , tablespace boolean + 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 + ( role regrole + , pretty boolean + DEFAULT false + , memberships boolean + 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 + ( tablespace oid + , pretty boolean + DEFAULT false + , owner boolean + DEFAULT true ) + setof text + + + pg_get_tablespace_ddl + ( tablespace name + , pretty boolean + DEFAULT false + , owner boolean + 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_id regdatabase + , VARIADIC options + "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 }