From bcd2bde823cd161a14187dee2a013f66c0616b0a Mon Sep 17 00:00:00 2001 From: Akshay Joshi Date: Tue, 20 Jan 2026 16:40:30 +0530 Subject: [PATCH v8] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statements. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new system function, pg_get_database_ddl(database_name/database_oid, ddl_options), which reconstructs the CREATE DATABASE statement for a given database name or database oid. Supported ddl_options are 'pretty', 'owner=no/false/0', 'tablespace=no/false/0' and 'defaults=yes'. Usage: SELECT pg_get_database_ddl('postgres'); // Non pretty-formatted DDL SELECT pg_get_database_ddl(16835); // Non pretty-formatted DDL SELECT pg_get_database_ddl('postgres', 'pretty'); // pretty-formatted DDL SELECT pg_get_database_ddl('postgres', 'owner=no', 'tablespace=no'); // Omits the Owner and Tablespace clause from the DDL. SELECT pg_get_database_ddl('postgres', 'pretty', 'defaults'); // Includes clauses for parameters that are currently at their default values. Reference: PG-150 Author: Akshay Joshi Reviewed-by: Álvaro Herrera Reviewed-by: Quan Zongliang Reviewed-by: Japin Li Reviewed-by: Chao Li Reviewed-by: Euler Taveira --- doc/src/sgml/func/func-info.sgml | 78 +++++ src/backend/catalog/system_functions.sql | 6 + src/backend/utils/adt/ruleutils.c | 365 +++++++++++++++++++++++ src/include/catalog/pg_proc.dat | 6 + src/include/utils/ddl_defaults.h | 39 +++ src/test/regress/expected/database.out | 134 +++++++++ src/test/regress/sql/database.sql | 91 ++++++ 7 files changed, 719 insertions(+) create mode 100644 src/include/utils/ddl_defaults.h diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index 175f18315cd..d337d1b3fb6 100644 --- a/doc/src/sgml/func/func-info.sgml +++ b/doc/src/sgml/func/func-info.sgml @@ -3830,4 +3830,82 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} + + Get Object DDL Functions + + + 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 for each object type. + + + + Get Object DDL Functions + + + + + Function + + + Description + + + + + + + + + pg_get_database_ddl + + pg_get_database_ddl ( database_id regdatabase , VARIADIC ddl_options text[] ) + text + + + Reconstructs the CREATE DATABASE statement from the + system catalogs for a specified database. The first argument is the OID or + name of the database. The optional variadic argument is an array of text + flags to control the output. Supported options include + pretty, owner=no, + tablespace=no, and defaults=yes. + + + + +
+ + + The ddl_options for pg_get_database_ddl + provide fine-grained control over the generated SQL: + + + + pretty: Adds newlines and indentation for better readability. + + + + + owner=no: Omits the OWNER clause from + the reconstructed statement. + + + + + tablespace=no: Omits the TABLESPACE + clause from the reconstructed statement. + + + + + defaults=yes: Includes clauses for parameters that are + currently at their default values (e.g., CONNECTION LIMIT -1), + which are normally omitted for brevity. + + + + + +
+ diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index eb9e31ae1bf..16c0d52479a 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -657,6 +657,12 @@ LANGUAGE INTERNAL STRICT VOLATILE PARALLEL UNSAFE AS 'pg_replication_origin_session_setup'; +CREATE OR REPLACE FUNCTION + pg_get_database_ddl(database_id regdatabase, VARIADIC ddl_options text[] DEFAULT '{}') +RETURNS text +LANGUAGE internal +AS 'pg_get_database_ddl'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 033b625f3fc..b2773d1f3b2 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -28,6 +28,7 @@ #include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" +#include "catalog/pg_database.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" #include "catalog/pg_opclass.h" @@ -57,8 +58,10 @@ #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" #include "rewrite/rewriteSupport.h" +#include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/ddl_defaults.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/hsearch.h" @@ -89,11 +92,22 @@ #define PRETTYFLAG_INDENT 0x0002 #define PRETTYFLAG_SCHEMA 0x0004 +/* 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 + + /* Standard conversion of a "bool pretty" option to detailed flags */ #define GET_PRETTY_FLAGS(pretty) \ ((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \ : PRETTYFLAG_INDENT) +#define GET_DDL_PRETTY_FLAGS(pretty) \ + ((pretty) ? (PRETTYFLAG_PAREN | PRETTYFLAG_INDENT | PRETTYFLAG_SCHEMA) \ + : 0) + /* Default line length for pretty-print wrapping: 0 means wrap always */ #define WRAP_COLUMN_DEFAULT 0 @@ -546,6 +560,11 @@ static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan, deparse_context *context, bool showimplicit, bool needcomma); +static void get_formatted_string(StringInfo buf, + int prettyFlags, + int nSpaces, + const char *fmt,...) pg_attribute_printf(4, 5); +static char *pg_get_database_ddl_worker(Oid db_oid, ArrayType *ddl_options); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -13743,3 +13762,349 @@ get_range_partbound_string(List *bound_datums) return buf.data; } + +/* + * get_formatted_string + * + * Return a formatted version of the string. + * + * 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 */ + for (int i = 0; i < nSpaces; i++) + { + appendStringInfoChar(buf, ' '); + } + } + else + appendStringInfoChar(buf, ' '); + + va_start(args, fmt); + appendStringInfoVA(buf, fmt, args); + va_end(args); +} + +/** + * parse_ddl_options - Generic helper to parse variadic text options + * ddl_options: The ArrayType from PG_GETARG_ARRAYTYPE_P + * flags: Bitmask to set options while parsing DDL options. + */ +static uint64 +parse_ddl_options(ArrayType *ddl_options) +{ + uint64 flags = 0; + Datum *options; + bool *nulls; + int n_options; + int i; + + if (ddl_options == NULL) + return flags; + + deconstruct_array(ddl_options, TEXTOID, -1, false, 'i', + &options, &nulls, &n_options); + + for (i = 0; i < n_options; i++) + { + char *opt; + char *name; + char *value; + + if (nulls[i]) + continue; + + opt = TextDatumGetCString(options[i]); + name = opt; + value = strchr(opt, '='); + + if (value != NULL) + { + *value = '\0'; + value++; + } + + /* + * * Logic for "owner": handle 'owner=no', 'owner=0', 'owner=false', + * etc. Using pg_strcasecmp for the key and parse_bool for the value. + */ + if (pg_strcasecmp(name, "owner") == 0) + { + bool bval; + + if (value == NULL) + continue; + + if (parse_bool(value, &bval)) + { + if (!bval) + flags |= PG_DDL_NO_OWNER; + } + else + goto invalid_value; + } + /* Logic for "tablespace" */ + else if (pg_strcasecmp(name, "tablespace") == 0) + { + bool bval; + + if (value == NULL) + continue; + + if (parse_bool(value, &bval)) + { + if (!bval) + flags |= PG_DDL_NO_TABLESPACE; + } + else + goto invalid_value; + } + /* Logic for "defaults" */ + else if (pg_strcasecmp(name, "defaults") == 0) + { + bool bval; + + /* If just 'defaults' is passed without '=val', we assume true */ + if (value == NULL) + flags |= PG_DDL_WITH_DEFAULTS; + else if (parse_bool(value, &bval)) + { + if (bval) + flags |= PG_DDL_WITH_DEFAULTS; + } + else + goto invalid_value; + } + /* Logic for "pretty" */ + else if (pg_strcasecmp(name, "pretty") == 0) + { + /* Usually a standalone flag, but we check boolean if provided */ + bool bval; + + if (value == NULL) + flags |= PG_DDL_PRETTY_INDENT; + else if (parse_bool(value, &bval)) + { + if (bval) + flags |= PG_DDL_PRETTY_INDENT; + } + else + goto invalid_value; + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized option: %s", name))); + } + + pfree(opt); + continue; + +invalid_value: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for option \"%s\": %s", name, value))); + } + + pfree(options); + pfree(nulls); + + return flags; +} + +/* + * pg_get_database_ddl + * + * Generate a CREATE DATABASE statement for the specified database name or oid. + * + * db_oid - OID/Name of the database for which to generate the DDL. + * ddl_options - Array of text options to modify the output. + */ +Datum +pg_get_database_ddl(PG_FUNCTION_ARGS) +{ + Oid db_oid = PG_GETARG_OID(0); + ArrayType *ddl_options = PG_GETARG_ARRAYTYPE_P(1); + char *res; + + res = pg_get_database_ddl_worker(db_oid, ddl_options); + + if (res == NULL) + PG_RETURN_NULL(); + + PG_RETURN_TEXT_P(string_to_text(res)); +} + +static char * +pg_get_database_ddl_worker(Oid db_oid, ArrayType *ddl_options) +{ + char *dbowner = NULL; + bool attr_isnull; + Datum dbvalue; + HeapTuple tuple_database; + Form_pg_database dbform; + StringInfoData buf; + AclResult aclresult; + + /* Variables for ddl_options parsing */ + int pretty_flags = 0; + uint64 ddl_flags = 0; + bool is_with_defaults = false; + + /* Call DDL options parser */ + ddl_flags = parse_ddl_options(ddl_options); + + /* 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) ? true : false; + + /* + * 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); + + /* Look up the owner in the system catalog */ + if (OidIsValid(dbform->datdba)) + dbowner = GetUserNameFromId(dbform->datdba, false); + + /* Build the CREATE DATABASE statement */ + appendStringInfo(&buf, "CREATE DATABASE %s", + quote_identifier(dbform->datname.data)); + get_formatted_string(&buf, pretty_flags, 4, "WITH"); + + /* Set the OWNER in the DDL if --no-owner is not specified */ + if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER)) + { + get_formatted_string(&buf, pretty_flags, 8, "OWNER = %s", + quote_identifier(dbowner)); + } + + /* Set the ENCODING in the DDL */ + if (dbform->encoding != 0) + { + /* If it's with defaults, we skip default encoding check */ + if (is_with_defaults || + (pg_strcasecmp(pg_encoding_to_char(dbform->encoding), + DDL_DEFAULTS.DATABASE.ENCODING) != 0)) + { + get_formatted_string(&buf, pretty_flags, 8, "ENCODING = %s", + quote_literal_cstr( + pg_encoding_to_char(dbform->encoding))); + } + } + + /* Fetch the value of LC_COLLATE */ + dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database, + Anum_pg_database_datcollate, &attr_isnull); + if (!attr_isnull) + get_formatted_string(&buf, pretty_flags, 8, "LC_COLLATE = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + /* Fetch the value of LC_CTYPE */ + dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database, + Anum_pg_database_datctype, &attr_isnull); + if (!attr_isnull) + get_formatted_string(&buf, pretty_flags, 8, "LC_CTYPE = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + /* Fetch the value of LOCALE */ + dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database, + Anum_pg_database_datlocale, &attr_isnull); + if (!attr_isnull && dbform->datlocprovider == COLLPROVIDER_BUILTIN) + get_formatted_string(&buf, pretty_flags, 8, "BUILTIN_LOCALE = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + else if (!attr_isnull && dbform->datlocprovider == COLLPROVIDER_ICU) + get_formatted_string(&buf, pretty_flags, 8, "ICU_LOCALE = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + else if (!attr_isnull) + get_formatted_string(&buf, pretty_flags, 8, "LOCALE = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + + /* 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(&buf, pretty_flags, 8, "ICU_RULES = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + + /* Fetch the value of COLLATION_VERSION */ + dbvalue = SysCacheGetAttr(DATABASEOID, tuple_database, + Anum_pg_database_datcollversion, &attr_isnull); + if (!attr_isnull) + get_formatted_string(&buf, pretty_flags, 8, "COLLATION_VERSION = %s", + quote_literal_cstr(TextDatumGetCString(dbvalue))); + + /* Set the appropriate LOCALE_PROVIDER */ + if (dbform->datlocprovider == COLLPROVIDER_BUILTIN) + get_formatted_string(&buf, pretty_flags, 8, "LOCALE_PROVIDER = builtin"); + else if (dbform->datlocprovider == COLLPROVIDER_ICU) + get_formatted_string(&buf, pretty_flags, 8, "LOCALE_PROVIDER = icu"); + else if (is_with_defaults) + get_formatted_string(&buf, pretty_flags, 8, "LOCALE_PROVIDER = libc"); + + /* Set the TABLESPACE in the DDL if --no-tablespace is not specified */ + if (OidIsValid(dbform->dattablespace) && !(ddl_flags & PG_DDL_NO_TABLESPACE)) + { + /* Get the tablespace name respective to the given tablespace oid */ + char *dbTablespace = get_tablespace_name(dbform->dattablespace); + + /* If it's with defaults, we skip default tablespace check */ + if (is_with_defaults || + (pg_strcasecmp(dbTablespace, DDL_DEFAULTS.DATABASE.TABLESPACE) != 0)) + get_formatted_string(&buf, pretty_flags, 8, "TABLESPACE = %s", + quote_identifier(dbTablespace)); + } + + if (is_with_defaults || + (dbform->datallowconn != DDL_DEFAULTS.DATABASE.ALLOW_CONN)) + { + get_formatted_string(&buf, pretty_flags, 8, "ALLOW_CONNECTIONS = %s", + dbform->datallowconn ? "true" : "false"); + } + + if (is_with_defaults || + (dbform->datconnlimit != DDL_DEFAULTS.DATABASE.CONN_LIMIT)) + { + get_formatted_string(&buf, pretty_flags, 8, "CONNECTION LIMIT = %d", + dbform->datconnlimit); + } + + if (dbform->datistemplate) + get_formatted_string(&buf, pretty_flags, 8, "IS_TEMPLATE = %s", + dbform->datistemplate ? "true" : "false"); + + appendStringInfoChar(&buf, ';'); + + ReleaseSysCache(tuple_database); + + return buf.data; +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 894b6a1b6d6..53d623de58f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -4030,6 +4030,12 @@ proname => 'pg_get_function_sqlbody', provolatile => 's', prorettype => 'text', proargtypes => 'oid', prosrc => 'pg_get_function_sqlbody' }, +{ oid => '9492', descr => 'get CREATE statement for database name and oid', + proname => 'pg_get_database_ddl', proisstrict => 'f', prorettype => 'text', + proargtypes => 'regdatabase _text', + proargmodes => '{i,v}', + proallargtypes => '{regdatabase,_text}', + prosrc => 'pg_get_database_ddl' }, { oid => '1686', descr => 'list of SQL keywords', proname => 'pg_get_keywords', procost => '10', prorows => '500', diff --git a/src/include/utils/ddl_defaults.h b/src/include/utils/ddl_defaults.h new file mode 100644 index 00000000000..d17e843fe09 --- /dev/null +++ b/src/include/utils/ddl_defaults.h @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------- + * + * ddl_defaults.h + * Declarations for DDL defaults. + * + * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/ddl_defaults.h + * + *------------------------------------------------------------------------- + */ +#ifndef DDL_DEFAULTS_H +#define DDL_DEFAULTS_H + +#include + +static const struct +{ + struct + { + const char *ENCODING; + const char *TABLESPACE; + int CONN_LIMIT; + bool ALLOW_CONN; + } DATABASE; + + /* Add more object types as needed */ +} DDL_DEFAULTS = { + + .DATABASE = { + .ENCODING = "UTF8", + .TABLESPACE = "pg_default", + .CONN_LIMIT = -1, + .ALLOW_CONN = true, + } +}; + +#endif /* DDL_DEFAULTS_H */ diff --git a/src/test/regress/expected/database.out b/src/test/regress/expected/database.out index 6b879b0f62a..a816b0b525f 100644 --- a/src/test/regress/expected/database.out +++ b/src/test/regress/expected/database.out @@ -1,3 +1,57 @@ +-- +-- Reconstruct DDL +-- +-- To produce stable regression test output, it's usually necessary to +-- ignore collation and locale related details. This filter +-- functions removes collation and locale related details. +CREATE OR REPLACE FUNCTION ddl_filter(ddl_input TEXT) +RETURNS TEXT AS $$ +DECLARE + cleaned_ddl TEXT; +BEGIN + -- Remove %LOCALE_PROVIDER% placeholders + cleaned_ddl := regexp_replace( + ddl_input, + '\s*\mLOCALE_PROVIDER\M\s*=\s*([''"]?[^''"\s]+[''"]?)', + '', + 'gi' + ); + + -- Remove LC_COLLATE assignments + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*LC_COLLATE\s*=\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove LC_CTYPE assignments + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*LC_CTYPE\s*=\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove %LOCALE% placeholders + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*\S*LOCALE\S*\s*=?\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove %COLLATION% placeholders + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*\S*COLLATION\S*\s*=?\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + RETURN cleaned_ddl; +END; +$$ LANGUAGE plpgsql; CREATE DATABASE regression_tbd ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; ALTER DATABASE regression_tbd RENAME TO regression_utf8; @@ -16,6 +70,86 @@ CREATE ROLE regress_datdba_before; CREATE ROLE regress_datdba_after; ALTER DATABASE regression_utf8 OWNER TO regress_datdba_before; REASSIGN OWNED BY regress_datdba_before TO regress_datdba_after; +-- Test pg_get_database_ddl +-- Database doesn't exists +SELECT pg_get_database_ddl('regression_database'); +ERROR: database "regression_database" does not exist +LINE 1: SELECT pg_get_database_ddl('regression_database'); + ^ +-- Test NULL value +SELECT pg_get_database_ddl(NULL); +ERROR: database with oid 0 does not exist +-- Without pretty +SELECT ddl_filter(pg_get_database_ddl('regression_utf8')); + ddl_filter +------------------------------------------------------------------------------------------- + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after CONNECTION LIMIT = 123; +(1 row) + +-- With No Owner +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner=no')); + ddl_filter +-------------------------------------------------------------- + CREATE DATABASE regression_utf8 WITH CONNECTION LIMIT = 123; +(1 row) + +-- With No Tablespace +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', 'tablespace=no')); + ddl_filter +-------------------------------------------------------------------------------------------------------------------------------------- + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123; +(1 row) + +-- With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults=yes')); + ddl_filter +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123; +(1 row) + +-- With No Owner, No Tablespace and With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner=0', 'tablespace=0', 'defaults=1')); + ddl_filter +--------------------------------------------------------------------------------------------------------- + CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123; +(1 row) + +-- With Pretty formatted +\pset format unaligned +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty')); +ddl_filter +CREATE DATABASE regression_utf8 + WITH + OWNER = regress_datdba_after + CONNECTION LIMIT = 123; +(1 row) +-- With No Owner and No Tablespace +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'owner=no', 'tablespace=no')); +ddl_filter +CREATE DATABASE regression_utf8 + WITH + CONNECTION LIMIT = 123; +(1 row) +-- With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'defaults')); +ddl_filter +CREATE DATABASE regression_utf8 + WITH + OWNER = regress_datdba_after + ENCODING = 'UTF8' + TABLESPACE = pg_default + ALLOW_CONNECTIONS = true + CONNECTION LIMIT = 123; +(1 row) +-- With No Owner, No Tablespace and With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'owner=false', 'tablespace=false', 'defaults=true')); +ddl_filter +CREATE DATABASE regression_utf8 + WITH + ENCODING = 'UTF8' + ALLOW_CONNECTIONS = true + CONNECTION LIMIT = 123; +(1 row) DROP DATABASE regression_utf8; DROP ROLE regress_datdba_before; DROP ROLE regress_datdba_after; diff --git a/src/test/regress/sql/database.sql b/src/test/regress/sql/database.sql index 4ef36127291..4cb17a8d0d6 100644 --- a/src/test/regress/sql/database.sql +++ b/src/test/regress/sql/database.sql @@ -1,3 +1,59 @@ +-- +-- Reconstruct DDL +-- +-- To produce stable regression test output, it's usually necessary to +-- ignore collation and locale related details. This filter +-- functions removes collation and locale related details. + +CREATE OR REPLACE FUNCTION ddl_filter(ddl_input TEXT) +RETURNS TEXT AS $$ +DECLARE + cleaned_ddl TEXT; +BEGIN + -- Remove %LOCALE_PROVIDER% placeholders + cleaned_ddl := regexp_replace( + ddl_input, + '\s*\mLOCALE_PROVIDER\M\s*=\s*([''"]?[^''"\s]+[''"]?)', + '', + 'gi' + ); + + -- Remove LC_COLLATE assignments + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*LC_COLLATE\s*=\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove LC_CTYPE assignments + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*LC_CTYPE\s*=\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove %LOCALE% placeholders + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*\S*LOCALE\S*\s*=?\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + -- Remove %COLLATION% placeholders + cleaned_ddl := regexp_replace( + cleaned_ddl, + '\s*\S*COLLATION\S*\s*=?\s*([''"])[^''"]*\1', + '', + 'gi' + ); + + RETURN cleaned_ddl; +END; +$$ LANGUAGE plpgsql; + CREATE DATABASE regression_tbd ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0; ALTER DATABASE regression_tbd RENAME TO regression_utf8; @@ -19,6 +75,41 @@ CREATE ROLE regress_datdba_after; ALTER DATABASE regression_utf8 OWNER TO regress_datdba_before; REASSIGN OWNED BY regress_datdba_before TO regress_datdba_after; +-- Test pg_get_database_ddl +-- Database doesn't exists +SELECT pg_get_database_ddl('regression_database'); + +-- Test NULL value +SELECT pg_get_database_ddl(NULL); + +-- Without pretty +SELECT ddl_filter(pg_get_database_ddl('regression_utf8')); + +-- With No Owner +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner=no')); + +-- With No Tablespace +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', 'tablespace=no')); + +-- With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults=yes')); + +-- With No Owner, No Tablespace and With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner=0', 'tablespace=0', 'defaults=1')); + +-- With Pretty formatted +\pset format unaligned +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty')); + +-- With No Owner and No Tablespace +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'owner=no', 'tablespace=no')); + +-- With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'defaults')); + +-- With No Owner, No Tablespace and With Defaults +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'owner=false', 'tablespace=false', 'defaults=true')); + DROP DATABASE regression_utf8; DROP ROLE regress_datdba_before; DROP ROLE regress_datdba_after; -- 2.51.0