From 07bf3ed7d7c299cd6aeb955398373ed79ec0df4a Mon Sep 17 00:00:00 2001
From: Akshay Joshi <akshay.joshi@enterprisedb.com>
Date: Wed, 21 Jan 2026 14:21:28 +0530
Subject: [PATCH v8 2/2] 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'.

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=false'); -- Omits Owner and Tablespace clauses.
  SELECT pg_get_database_ddl('postgres', 'pretty', 'defaults=yes'); -- Includes clauses for parameters at their default values.
  SELECT pg_get_database_ddl('postgres', 'pretty', 'defaults'); -- Includes clauses for parameters at their default values.

Reference: PG-150
Author: Akshay Joshi <akshay.joshi@enterprisedb.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Reviewed-by: Quan Zongliang <quanzongliang@yeah.net>
Reviewed-by: Japin Li <japinli@hotmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Euler Taveira <euler@eulerto.com>
---
 doc/src/sgml/func/func-info.sgml       |  17 ++--
 src/backend/utils/adt/ruleutils.c      | 103 +++++++++++++++++++++----
 src/include/utils/ddl_defaults.h       |   4 +-
 src/test/regress/expected/database.out |  20 ++---
 src/test/regress/sql/database.sql      |  14 ++--
 5 files changed, 120 insertions(+), 38 deletions(-)

diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml
index 2250031bb83..71a60dbc11c 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3859,7 +3859,10 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
         <indexterm>
          <primary>pg_get_database_ddl</primary>
         </indexterm>
-        <function>pg_get_database_ddl</function> ( <parameter>database_id</parameter> <type>regdatabase</type> <optional>, <literal>VARIADIC</literal> <parameter>ddl_options</parameter> <type>text[]</type> </optional> )
+        <function>pg_get_database_ddl</function>
+        ( <parameter>database_id</parameter> <type>regdatabase</type>
+        <optional>, <literal>VARIADIC</literal> <parameter>ddl_options</parameter>
+        <type>text[]</type> </optional> )
         <returnvalue>text</returnvalue>
        </para>
        <para>
@@ -3867,8 +3870,9 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
         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
-        <literal>pretty</literal>, <literal>--no-owner</literal>,
-        <literal>--no-tablespace</literal>, and <literal>--with-defaults</literal>.
+        <literal>pretty</literal>, <literal>owner=no/false/0</literal>,
+        <literal>tablespace=no/false/0</literal>, and <literal>defaults</literal>
+        Or <literal>defaults=yes</literal>.
         </para></entry>
       </row>
      </tbody>
@@ -3886,18 +3890,19 @@ acl      | {postgres=arwdDxtm/postgres,foo=r/postgres}
     </listitem>
     <listitem>
       <para>
-      <literal>--no-owner</literal>: Omits the <literal>OWNER</literal> clause from
+      <literal>owner=no/false/0</literal>: Omits the <literal>OWNER</literal> clause from
       the reconstructed statement.
       </para>
     </listitem>
     <listitem>
       <para>
-      <literal>--no-tablespace</literal>: Omits the <literal>TABLESPACE</literal> clause.
+      <literal>tablespace=no/false/0</literal>: Omits the <literal>TABLESPACE</literal>
+      clause from the reconstructed statement.
       </para>
     </listitem>
     <listitem>
       <para>
-      <literal>--with-defaults</literal>: Includes clauses for parameters that are
+      <literal>defaults</literal>: Includes clauses for parameters that are
       currently at their default values (e.g., <literal>CONNECTION LIMIT -1</literal>),
       which are normally omitted for brevity.
       </para>
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c6b59790655..8181010224a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13813,34 +13813,109 @@ parse_ddl_options(ArrayType *ddl_options)
 	if (ddl_options == NULL)
 		return flags;
 
-	deconstruct_array(ddl_options,
-					  TEXTOID, -1, false, 'i',
+	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;
 
-		/* Map strings to bitmask flags */
-		if (strcmp(opt, "pretty") == 0)
-			flags |= PG_DDL_PRETTY_INDENT;
-		else if (strcmp(opt, "--no-owner") == 0)
-			flags |= PG_DDL_NO_OWNER;
-		else if (strcmp(opt, "--no-tablespace") == 0)
-			flags |= PG_DDL_NO_TABLESPACE;
-		else if (strcmp(opt, "--with-defaults") == 0)
-			flags |= PG_DDL_WITH_DEFAULTS;
+			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", opt)));
+					 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);
@@ -13929,7 +14004,7 @@ pg_get_database_ddl_worker(Oid db_oid, ArrayType *ddl_options)
 					 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 */
+	/* Set the OWNER in the DDL if owner is not omitted */
 	if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
 	{
 		get_formatted_string(&buf, pretty_flags, 8, "OWNER = %s",
@@ -13997,7 +14072,7 @@ pg_get_database_ddl_worker(Oid db_oid, ArrayType *ddl_options)
 	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 */
+	/* Set the TABLESPACE in the DDL if tablespace is not omitted */
 	if (OidIsValid(dbform->dattablespace) && !(ddl_flags & PG_DDL_NO_TABLESPACE))
 	{
 		/* Get the tablespace name respective to the given tablespace oid */
diff --git a/src/include/utils/ddl_defaults.h b/src/include/utils/ddl_defaults.h
index 84ef61b4f3d..d17e843fe09 100644
--- a/src/include/utils/ddl_defaults.h
+++ b/src/include/utils/ddl_defaults.h
@@ -13,6 +13,8 @@
 #ifndef DDL_DEFAULTS_H
 #define DDL_DEFAULTS_H
 
+#include <stdbool.h>
+
 static const struct
 {
 	struct
@@ -34,4 +36,4 @@ static const struct
 	}
 };
 
-#endif						/* DDL_DEFAULTS_H */
\ No newline at end of file
+#endif							/* DDL_DEFAULTS_H */
diff --git a/src/test/regress/expected/database.out b/src/test/regress/expected/database.out
index eb3a13bcf9e..a816b0b525f 100644
--- a/src/test/regress/expected/database.out
+++ b/src/test/regress/expected/database.out
@@ -87,28 +87,28 @@ SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
 (1 row)
 
 -- With No Owner
-SELECT ddl_filter(pg_get_database_ddl('regression_utf8', '--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', '--no-tablespace'));
-                                        ddl_filter                                         
--------------------------------------------------------------------------------------------
- CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after CONNECTION LIMIT = 123;
+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', '--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', '--no-owner', '--no-tablespace', '--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;
@@ -124,14 +124,14 @@ CREATE DATABASE regression_utf8
         CONNECTION LIMIT = 123;
 (1 row)
 -- With No Owner and No Tablespace
-SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', '--no-owner', '--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', '--with-defaults'));
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', 'defaults'));
 ddl_filter
 CREATE DATABASE regression_utf8
     WITH
@@ -142,7 +142,7 @@ CREATE DATABASE regression_utf8
         CONNECTION LIMIT = 123;
 (1 row)
 -- With No Owner, No Tablespace and With Defaults
-SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', '--no-owner', '--no-tablespace', '--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
diff --git a/src/test/regress/sql/database.sql b/src/test/regress/sql/database.sql
index 918b28c47da..4cb17a8d0d6 100644
--- a/src/test/regress/sql/database.sql
+++ b/src/test/regress/sql/database.sql
@@ -86,29 +86,29 @@ SELECT pg_get_database_ddl(NULL);
 SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
 
 -- With No Owner
-SELECT ddl_filter(pg_get_database_ddl('regression_utf8', '--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', '--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', '--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', '--no-owner', '--no-tablespace', '--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', '--no-owner', '--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', '--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', '--no-owner', '--no-tablespace', '--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;
-- 
2.51.0

