agora inbox for [email protected]
help / color / mirror / Atom feedRe: Refactor handling of database attributes between pg_dump and pg_dumpall
67+ messages / 9 participants
[nested] [flat]
* Re: Refactor handling of database attributes between pg_dump and pg_dumpall
@ 2017-04-04 05:06 Haribabu Kommi <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Haribabu Kommi @ 2017-04-04 05:06 UTC (permalink / raw)
To: Andreas Karlsson <[email protected]>; +Cc: pgsql-hackers
On Thu, Mar 30, 2017 at 12:00 PM, Haribabu Kommi <[email protected]>
wrote:
>
>
> On Wed, Mar 29, 2017 at 11:04 PM, Andreas Karlsson <[email protected]>
> wrote:
>
>> On 03/29/2017 05:43 AM, Haribabu Kommi wrote:
>> > Updated patch attached.
>>
>> I get a test failure in the pg_upgrade tests, but I do not have time
>> right now to investigate.
>>
>> The failing test is "Restoring database schemas in the new cluster".
>>
>
> Thanks for test.
>
> I found the reason for failure.
>
> Before this refactor patch, in case of --binary-upgrade, the pg_dumpall
> dumps all the global objects and also the database objects. These objects
> will be restored first during the preparation of the new cluster and later
> each individual database is restored.
>
> Because of the refactoring of the database objects, currently as part of
> globals dump with --binary-upgrade, no database objects gets dumped.
> During restore no databases are created. so while restoring individual
> database, it leads to failure as it not able to connect to the target
> database.
>
I modified the pg_upgrade code to use template1 database as a connecting
database while restoring the dump along with --create option to pg_restore
to create the database objects instead of connecting to the each individual
database.
And also while dumping the database objects, passed the new option of
--enable-pgdumpall-behaviour to pg_dump to dump the database objects
as it expected dump during pg_dumpall --binary-upgrade.
Both pg_dump and pg_upgrade tests are passed. Updated patch attached
I will add this patch to the next commitfest.
Regards,
Hari Babu
Fujitsu Australia
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Attachments:
[application/octet-stream] pg_dump_changes_5.patch (47.3K, ../../CAJrrPGdXP3=hjX7+N1Xa0U2VFOxf54aHqV8HR7ji3AntsXmZuA@mail.gmail.com/3-pg_dump_changes_5.patch)
download | inline diff:
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 4f19b89..3921137 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -192,8 +192,11 @@ PostgreSQL documentation
database itself and reconnect to the created database. (With a
script of this form, it doesn't matter which database in the
destination installation you connect to before running the script.)
- If <option>--clean</option> is also specified, the script drops and
- recreates the target database before reconnecting to it.
+ It also sets all the database-level properties such as ownership,
+ ACLs, <command>ALTER DATABASE ... SET</command> commands, and
+ <command>ALTER ROLE IN DATABASE ... SET</command> commands.
+ If <option>--clean</option> is also specified, the script drops
+ and recreates the target database before reconnecting to it.
</para>
<para>
@@ -705,6 +708,20 @@ PostgreSQL documentation
</varlistentry>
<varlistentry>
+ <term><option>--enable-pgdumpall-behaviour</option></term>
+ <listitem>
+ <para>
+ This option is for the use of <command>pg_dumpall</command> or
+ <command>pg_upgrade</command> utility to dump the database objects
+ by <application>pg_dump</application> for a complete dump.
+ This option can only be used with <option>-C/--create</option>.
+ Its use for other ourposes is not recommended or supported.
+ The behavior of the option may change in future releases without notice.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--enable-row-security</></term>
<listitem>
<para>
@@ -1218,10 +1235,6 @@ CREATE DATABASE foo WITH TEMPLATE template0;
<command>ANALYZE</command> after restoring from a dump file
to ensure optimal performance; see <xref linkend="vacuum-for-statistics">
and <xref linkend="autovacuum"> for more information.
- The dump file also does not
- contain any <command>ALTER DATABASE ... SET</> commands;
- these settings are dumped by <xref linkend="app-pg-dumpall">,
- along with database users and other installation-wide settings.
</para>
<para>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index 070b902..d929551 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -116,6 +116,10 @@ PostgreSQL documentation
<listitem>
<para>
Dump only global objects (roles and tablespaces), no databases.
+ Any database role configuration settings are not dumped with
+ this option. User needs to use <command>pg_dump</command> with
+ <option>--create</option> option to dump <>ALTER ROLE IN DATABASE ... SET</>
+ commands.
</para>
</listitem>
</varlistentry>
diff --git a/src/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index b58636e..9f559d8 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -31,8 +31,8 @@ pg_dump: pg_dump.o common.o pg_dump_sort.o $(OBJS) | submake-libpq submake-libpg
pg_restore: pg_restore.o $(OBJS) | submake-libpq submake-libpgport submake-libpgfeutils
$(CC) $(CFLAGS) pg_restore.o $(OBJS) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
-pg_dumpall: pg_dumpall.o dumputils.o | submake-libpq submake-libpgport submake-libpgfeutils
- $(CC) $(CFLAGS) pg_dumpall.o dumputils.o $(WIN32RES) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+pg_dumpall: pg_dumpall.o dumputils.o pg_backup_utils.o | submake-libpq submake-libpgport submake-libpgfeutils
+ $(CC) $(CFLAGS) pg_dumpall.o dumputils.o pg_backup_utils.o $(WIN32RES) $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
install: all installdirs
$(INSTALL_PROGRAM) pg_dump$(X) '$(DESTDIR)$(bindir)'/pg_dump$(X)
diff --git a/src/bin/pg_dump/dumputils.c b/src/bin/pg_dump/dumputils.c
index c74153a..b714ab5 100644
--- a/src/bin/pg_dump/dumputils.c
+++ b/src/bin/pg_dump/dumputils.c
@@ -778,3 +778,70 @@ buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
printfPQExpBuffer(init_racl_subquery, "NULL");
}
}
+
+/*
+ * Helper function for dumpXXXConfig().
+ *
+ * Frame the ALTER ".." SET ".." commands and fill it in buf.
+ */
+void
+makeAlterConfigCommand(PGconn *conn, const char *configitem,
+ const char *type, const char *name,
+ const char *type2, const char *name2,
+ PQExpBuffer buf)
+{
+ char *pos;
+ char *mine;
+
+ mine = pg_strdup(configitem);
+ pos = strchr(mine, '=');
+ if (pos == NULL)
+ {
+ pg_free(mine);
+ return;
+ }
+
+ *pos++ = '\0';
+ appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
+ if (type2 != NULL && name2 != NULL)
+ appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
+ appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
+
+ /*
+ * Some GUC variable names are 'LIST' type and hence must not be quoted.
+ */
+ if (pg_strcasecmp(mine, "DateStyle") == 0
+ || pg_strcasecmp(mine, "search_path") == 0)
+ appendPQExpBufferStr(buf, pos);
+ else
+ appendStringLiteralConn(buf, pos, conn);
+ appendPQExpBufferStr(buf, ";\n");
+
+ pg_free(mine);
+}
+
+/*
+ * Run a query, return the results, exit program on failure.
+ */
+PGresult *
+executeQuery(PGconn *conn, const char *query)
+{
+ PGresult *res;
+
+ if (g_verbose)
+ fprintf(stderr, _("%s: executing %s\n"), progname, query);
+
+ res = PQexec(conn, query);
+ if (!res ||
+ PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ fprintf(stderr, _("%s: query failed: %s"),
+ progname, PQerrorMessage(conn));
+ fprintf(stderr, _("%s: query was: %s\n"),
+ progname, query);
+ PQfinish(conn);
+ exit_nicely(1);
+ }
+
+ return res;
+}
diff --git a/src/bin/pg_dump/dumputils.h b/src/bin/pg_dump/dumputils.h
index d7eecdf..80d68f9 100644
--- a/src/bin/pg_dump/dumputils.h
+++ b/src/bin/pg_dump/dumputils.h
@@ -17,6 +17,7 @@
#include "libpq-fe.h"
#include "pqexpbuffer.h"
+#include "pg_backup_utils.h"
/*
* Preferred strftime(3) format specifier for printing timestamps in pg_dump
@@ -56,4 +57,10 @@ extern void buildACLQueries(PQExpBuffer acl_subquery, PQExpBuffer racl_subquery,
const char *acl_column, const char *acl_owner,
const char *obj_kind, bool binary_upgrade);
+extern void makeAlterConfigCommand(PGconn *conn, const char *arrayitem,
+ const char *type, const char *name, const char *type2,
+ const char *name2, PQExpBuffer buf);
+
+extern PGresult *executeQuery(PGconn *conn, const char *query);
+
#endif /* DUMPUTILS_H */
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 98bc1a5..3596750 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -121,6 +121,7 @@ typedef struct _restoreOptions
int sequence_data; /* dump sequence data even in schema-only mode */
int include_subscriptions;
int binary_upgrade;
+ int enable_pgdumpall_behaviour;
} RestoreOptions;
typedef struct _dumpOptions
@@ -156,6 +157,7 @@ typedef struct _dumpOptions
int enable_row_security;
int include_subscriptions;
int no_subscription_connect;
+ int enable_pgdumpall_behaviour;
/* default, if no "inclusion" switches appear, is to dump everything */
bool include_everything;
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index f77581d..e2669be 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -804,6 +804,8 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel)
ahlog(AH, 1, "connecting to new database \"%s\"\n", te->tag);
_reconnectToDB(AH, te->tag);
+ if (ropt->enable_pgdumpall_behaviour)
+ ahprintf(AH, "SET default_transaction_read_only = off;\n\n");
ropt->dbname = connstr.data;
}
}
diff --git a/src/bin/pg_dump/pg_backup_utils.c b/src/bin/pg_dump/pg_backup_utils.c
index d7907fd..e848ca5 100644
--- a/src/bin/pg_dump/pg_backup_utils.c
+++ b/src/bin/pg_dump/pg_backup_utils.c
@@ -19,6 +19,9 @@
/* Globals exported by this file */
const char *progname = NULL;
+/* User wants verbose narration of our activities */
+bool g_verbose = false;
+
#define MAX_ON_EXIT_NICELY 20
static struct
diff --git a/src/bin/pg_dump/pg_backup_utils.h b/src/bin/pg_dump/pg_backup_utils.h
index 04b53f4..4e2296f 100644
--- a/src/bin/pg_dump/pg_backup_utils.h
+++ b/src/bin/pg_dump/pg_backup_utils.h
@@ -26,6 +26,7 @@ typedef enum /* bits returned by set_dump_section */
typedef void (*on_exit_nicely_callback) (int code, void *arg);
extern const char *progname;
+extern bool g_verbose;
extern void set_dump_section(const char *arg, int *dumpSections);
extern void write_msg(const char *modulename, const char *fmt,...) pg_attribute_printf(2, 3);
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 262f553..dba371f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -86,9 +86,6 @@ typedef enum OidOptions
zeroAsNone = 8
} OidOptions;
-/* global decls */
-bool g_verbose; /* User wants verbose narration of our
- * activities. */
static bool dosync = true; /* Issue fsync() to make dump durable
* on disk. */
@@ -269,7 +266,8 @@ static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
const char *prefix, Archive *fout);
static char *get_synchronized_snapshot(Archive *fout);
static void setupDumpWorker(Archive *AHX);
-
+static void dumpDatabaseConfig(Archive *fout, PQExpBuffer creaQry, const char *dbname);
+static void dumpDbRoleConfig(Archive *AH, PQExpBuffer creaQry);
int
main(int argc, char **argv)
@@ -357,6 +355,7 @@ main(int argc, char **argv)
{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
{"no-sync", no_argument, NULL, 7},
+ {"enable-pgdumpall-behaviour", no_argument, &dopt.enable_pgdumpall_behaviour, 1},
{NULL, 0, NULL, 0}
};
@@ -576,6 +575,12 @@ main(int argc, char **argv)
if (dopt.binary_upgrade)
dopt.sequence_data = 1;
+ if (dopt.enable_pgdumpall_behaviour && !dopt.outputCreateDB)
+ {
+ write_msg(NULL, "option --enable-pgdumpall-behaviour requires option -C/--create\n");
+ exit_nicely(1);
+ }
+
if (dopt.dataOnly && dopt.schemaOnly)
{
write_msg(NULL, "options -s/--schema-only and -a/--data-only cannot be used together\n");
@@ -869,6 +874,7 @@ main(int argc, char **argv)
ropt->sequence_data = dopt.sequence_data;
ropt->include_subscriptions = dopt.include_subscriptions;
ropt->binary_upgrade = dopt.binary_upgrade;
+ ropt->enable_pgdumpall_behaviour = dopt.enable_pgdumpall_behaviour;
if (compressLevel == -1)
ropt->compression = 0;
@@ -2449,43 +2455,141 @@ dumpDatabase(Archive *fout)
PQExpBuffer dbQry = createPQExpBuffer();
PQExpBuffer delQry = createPQExpBuffer();
PQExpBuffer creaQry = createPQExpBuffer();
+ PQExpBuffer aclQry = createPQExpBuffer();
PGconn *conn = GetConnection(fout);
PGresult *res;
int i_tableoid,
i_oid,
i_dba,
+ i_dbacl,
+ i_rdbacl,
i_encoding,
i_collate,
i_ctype,
i_frozenxid,
i_minmxid,
- i_tablespace;
+ i_tablespace,
+ i_dbistemplate,
+ i_dbconnlimit;
CatalogId dbCatId;
DumpId dbDumpId;
const char *datname,
*dba,
+ *dbacl,
+ *rdbacl,
*encoding,
*collate,
*ctype,
- *tablespace;
+ *tablespace,
+ *dbistemplate,
+ *dbconnlimit;
uint32 frozenxid,
minmxid;
+ char *default_encoding = NULL;
+ char *default_collate = NULL;
+ char *default_ctype = NULL;
datname = PQdb(conn);
if (g_verbose)
write_msg(NULL, "saving database definition\n");
+ if (dopt->enable_pgdumpall_behaviour)
+ {
+ /*
+ * First, get the installation's default encoding and locale
+ * information. We will dump encoding and locale specifications in the
+ * CREATE DATABASE commands for just those databases with values
+ * different from defaults.
+ *
+ * We consider template0's encoding and locale to define the
+ * installation default. Pre-8.4 installations do not have
+ * per-database locale settings; for them, every database must
+ * necessarily be using the installation default, so there's no need
+ * to do anything.
+ */
+
+ if (fout->remoteVersion >= 80400)
+ {
+ appendPQExpBuffer(dbQry,
+ "SELECT pg_encoding_to_char(encoding), "
+ "datcollate, datctype "
+ "FROM pg_database "
+ "WHERE datname = 'template0'");
+
+ res = ExecuteSqlQueryForSingleRow(fout, dbQry->data);
+ }
+ else
+ {
+ appendPQExpBuffer(dbQry,
+ "SELECT pg_encoding_to_char(encoding), "
+ "null::text AS datcollate, null::text AS datctype "
+ "FROM pg_database "
+ "WHERE datname = 'template0'");
+
+ res = ExecuteSqlQueryForSingleRow(fout, dbQry->data);
+ }
+ /* If for some reason the template DB isn't there, treat as unknown */
+ if (PQntuples(res) > 0)
+ {
+ if (!PQgetisnull(res, 0, 0))
+ default_encoding = pg_strdup(PQgetvalue(res, 0, 0));
+ if (!PQgetisnull(res, 0, 1))
+ default_collate = pg_strdup(PQgetvalue(res, 0, 1));
+ if (!PQgetisnull(res, 0, 2))
+ default_ctype = pg_strdup(PQgetvalue(res, 0, 2));
+ }
+
+ PQclear(res);
+ resetPQExpBuffer(dbQry);
+ }
+
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
- /* Get the database owner and parameters from pg_database */
- if (fout->remoteVersion >= 90300)
+ /*
+ * Now collect all the information about databases to dump.
+ *
+ * For the database ACLs, as of 9.6, we extract both the positive (as
+ * datacl) and negative (as rdatacl) ACLs, relative to the default ACL for
+ * databases, which are then passed to buildACLCommands() below.
+ *
+ * See buildACLQueries() and buildACLCommands().
+ *
+ * Note that we do not support initial privileges (pg_init_privs) on
+ * databases.
+ */
+
+ if (fout->remoteVersion >= 90600)
+ {
+ appendPQExpBuffer(dbQry,
+ "SELECT tableoid, oid, "
+ "(%s datdba) AS dba, "
+ "pg_encoding_to_char(encoding) AS encoding, "
+ "datcollate, datctype, datfrozenxid, datminmxid, datistemplate, "
+ "(SELECT pg_catalog.array_agg(acl ORDER BY acl::text COLLATE \"C\") FROM ( "
+ " SELECT pg_catalog.unnest(coalesce(datacl,pg_catalog.acldefault('d',datdba))) AS acl "
+ " EXCEPT SELECT pg_catalog.unnest(pg_catalog.acldefault('d',datdba))) as datacls)"
+ " AS datacl, "
+ "(SELECT pg_catalog.array_agg(acl ORDER BY acl::text COLLATE \"C\") FROM ( "
+ " SELECT pg_catalog.unnest(pg_catalog.acldefault('d',datdba)) AS acl "
+ " EXCEPT SELECT pg_catalog.unnest(coalesce(datacl,pg_catalog.acldefault('d',datdba)))) as rdatacls)"
+ " AS rdatacl, "
+ "datconnlimit, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
+ "shobj_description(oid, 'pg_database') AS description "
+
+ "FROM pg_database "
+ "WHERE datname = ",
+ username_subquery);
+ appendStringLiteralAH(dbQry, datname, fout);
+ }
+ else if (fout->remoteVersion >= 90300)
{
appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
"(%s datdba) AS dba, "
"pg_encoding_to_char(encoding) AS encoding, "
- "datcollate, datctype, datfrozenxid, datminmxid, "
+ "datcollate, datctype, datfrozenxid, datminmxid, datistemplate, datacl,'' as rdatacl, datconnlimit,"
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
"shobj_description(oid, 'pg_database') AS description "
@@ -2499,7 +2603,7 @@ dumpDatabase(Archive *fout)
appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
"(%s datdba) AS dba, "
"pg_encoding_to_char(encoding) AS encoding, "
- "datcollate, datctype, datfrozenxid, 0 AS datminmxid, "
+ "datcollate, datctype, datfrozenxid, 0 AS datminmxid, datistemplate, datacl, '' as rdatacl, datconnlimit,"
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
"shobj_description(oid, 'pg_database') AS description "
@@ -2513,7 +2617,7 @@ dumpDatabase(Archive *fout)
appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
"(%s datdba) AS dba, "
"pg_encoding_to_char(encoding) AS encoding, "
- "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, "
+ "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, datistemplate, datacl,'' as rdatacl, datconnlimit,"
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, "
"shobj_description(oid, 'pg_database') AS description "
@@ -2527,7 +2631,8 @@ dumpDatabase(Archive *fout)
appendPQExpBuffer(dbQry, "SELECT tableoid, oid, "
"(%s datdba) AS dba, "
"pg_encoding_to_char(encoding) AS encoding, "
- "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, "
+ "NULL AS datcollate, NULL AS datctype, datfrozenxid, 0 AS datminmxid, datistemplate, datacl,'' as rdatacl,"
+ "-1 as datconnlimit,"
"(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace "
"FROM pg_database "
"WHERE datname = ",
@@ -2546,6 +2651,10 @@ dumpDatabase(Archive *fout)
i_frozenxid = PQfnumber(res, "datfrozenxid");
i_minmxid = PQfnumber(res, "datminmxid");
i_tablespace = PQfnumber(res, "tablespace");
+ i_dbacl = PQfnumber(res, "datacl");
+ i_rdbacl = PQfnumber(res, "rdatacl");
+ i_dbistemplate = PQfnumber(res, "datistemplate");
+ i_dbconnlimit = PQfnumber(res, "datconnlimit");
dbCatId.tableoid = atooid(PQgetvalue(res, 0, i_tableoid));
dbCatId.oid = atooid(PQgetvalue(res, 0, i_oid));
@@ -2556,40 +2665,75 @@ dumpDatabase(Archive *fout)
frozenxid = atooid(PQgetvalue(res, 0, i_frozenxid));
minmxid = atooid(PQgetvalue(res, 0, i_minmxid));
tablespace = PQgetvalue(res, 0, i_tablespace);
+ dbacl = PQgetvalue(res, 0, i_dbacl);
+ rdbacl = PQgetvalue(res, 0, i_rdbacl);
+ dbistemplate = PQgetvalue(res, 0, i_dbistemplate);
+ dbconnlimit = PQgetvalue(res, 0, i_dbconnlimit);
- appendPQExpBuffer(creaQry, "CREATE DATABASE %s WITH TEMPLATE = template0",
- fmtId(datname));
- if (strlen(encoding) > 0)
- {
- appendPQExpBufferStr(creaQry, " ENCODING = ");
- appendStringLiteralAH(creaQry, encoding, fout);
- }
- if (strlen(collate) > 0)
+ /*
+ * Skip the CREATE DATABASE commands for "template1" and "postgres", since
+ * they are presumably already there in the destination cluster if
+ * enable_pgdumpall_behaviour is enabled. We do want to emit their ACLs
+ * and config options if any, however.
+ */
+ if (!dopt->enable_pgdumpall_behaviour ||
+ (strcmp(datname, "template1") != 0 && strcmp(datname, "postgres")) != 0)
{
- appendPQExpBufferStr(creaQry, " LC_COLLATE = ");
- appendStringLiteralAH(creaQry, collate, fout);
+ appendPQExpBuffer(creaQry, "CREATE DATABASE %s WITH TEMPLATE = template0",
+ fmtId(datname));
+
+ if (strlen(encoding) > 0 &&
+ (!dopt->enable_pgdumpall_behaviour ||
+ (default_encoding && strcmp(encoding, default_encoding) != 0)))
+ {
+ appendPQExpBufferStr(creaQry, " ENCODING = ");
+ appendStringLiteralAH(creaQry, encoding, fout);
+ }
+ if (strlen(collate) > 0 &&
+ (!dopt->enable_pgdumpall_behaviour ||
+ (default_collate && strcmp(collate, default_collate) != 0)))
+ {
+ appendPQExpBufferStr(creaQry, " LC_COLLATE = ");
+ appendStringLiteralAH(creaQry, collate, fout);
+ }
+ if (strlen(ctype) > 0 &&
+ (!dopt->enable_pgdumpall_behaviour ||
+ (default_ctype && strcmp(ctype, default_ctype) != 0)))
+ {
+ appendPQExpBufferStr(creaQry, " LC_CTYPE = ");
+ appendStringLiteralAH(creaQry, ctype, fout);
+ }
+ if (strlen(tablespace) > 0 && strcmp(tablespace, "pg_default") != 0 &&
+ !dopt->outputNoTablespaces)
+ {
+ appendPQExpBuffer(creaQry, " TABLESPACE = %s",
+ fmtId(tablespace));
+ }
+
+ if (dopt->enable_pgdumpall_behaviour)
+ {
+ if (strlen(dbistemplate) > 0 && strcmp(dbistemplate, "t") == 0)
+ appendPQExpBuffer(creaQry, " IS_TEMPLATE = true");
+
+ if (strlen(dbconnlimit) > 0 && strcmp(dbconnlimit, "-1") != 0)
+ appendPQExpBuffer(creaQry, " CONNECTION LIMIT = %s",
+ dbconnlimit);
+ }
+
+ appendPQExpBufferStr(creaQry, ";\n");
}
- if (strlen(ctype) > 0)
+ else if (strlen(tablespace) > 0 && strcmp(tablespace, "pg_default") != 0 && !dopt->outputNoTablespaces)
{
- appendPQExpBufferStr(creaQry, " LC_CTYPE = ");
- appendStringLiteralAH(creaQry, ctype, fout);
- }
- if (strlen(tablespace) > 0 && strcmp(tablespace, "pg_default") != 0 &&
- !dopt->outputNoTablespaces)
- appendPQExpBuffer(creaQry, " TABLESPACE = %s",
- fmtId(tablespace));
- appendPQExpBufferStr(creaQry, ";\n");
+ if (strcmp(datname, "postgres") == 0)
+ appendPQExpBuffer(creaQry, "\\connect template1\n");
+ else if (strcmp(datname, "template1") == 0)
+ appendPQExpBuffer(creaQry, "\\connect postgres\n");
- if (dopt->binary_upgrade)
- {
- appendPQExpBufferStr(creaQry, "\n-- For binary upgrade, set datfrozenxid and datminmxid.\n");
- appendPQExpBuffer(creaQry, "UPDATE pg_catalog.pg_database\n"
- "SET datfrozenxid = '%u', datminmxid = '%u'\n"
- "WHERE datname = ",
- frozenxid, minmxid);
- appendStringLiteralAH(creaQry, datname, fout);
- appendPQExpBufferStr(creaQry, ";\n");
+ appendPQExpBuffer(creaQry, "ALTER DATABASE %s SET TABLESPACE %s;\n",
+ datname, fmtId(tablespace));
+ /* connect to original database */
+ appendPsqlMetaConnect(creaQry, datname);
}
appendPQExpBuffer(delQry, "DROP DATABASE %s;\n",
@@ -2615,6 +2759,57 @@ dumpDatabase(Archive *fout)
NULL, /* Dumper */
NULL); /* Dumper Arg */
+ resetPQExpBuffer(creaQry);
+
+ if (dopt->binary_upgrade)
+ {
+ appendPQExpBufferStr(creaQry, "\n-- For binary upgrade, set datfrozenxid and datminmxid.\n");
+ appendPQExpBuffer(creaQry, "UPDATE pg_catalog.pg_database\n"
+ "SET datfrozenxid = '%u', datminmxid = '%u'\n"
+ "WHERE datname = ",
+ frozenxid, minmxid);
+ appendStringLiteralAH(creaQry, datname, fout);
+ appendPQExpBufferStr(creaQry, ";\n");
+
+ ArchiveEntry(fout, dbCatId, createDumpId(),
+ datname, NULL, NULL, dba,
+ false, "DATABASE", SECTION_PRE_DATA,
+ creaQry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+ resetPQExpBuffer(creaQry);
+ }
+
+ if (!dopt->aclsSkip &&
+ !buildACLCommands(datname, NULL, "DATABASE",
+ dbacl, rdbacl, dba,
+ "", fout->remoteVersion, aclQry))
+ {
+ exit_horribly(NULL, _("%s: could not parse ACL list (%s) for database \"%s\"\n"), progname, dbacl, datname);
+ }
+
+ if (strlen(aclQry->data))
+ ArchiveEntry(fout, dbCatId, createDumpId(),
+ datname, NULL, NULL, dba,
+ false, "DATABASE", SECTION_PRE_DATA,
+ aclQry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump database specific configuration */
+ dumpDatabaseConfig(fout, creaQry, datname);
+
+ /* Dump user and database specific configuration */
+ dumpDbRoleConfig(fout, creaQry);
+
+ if (strlen(creaQry->data))
+ ArchiveEntry(fout, dbCatId, createDumpId(),
+ datname, NULL, NULL, dba,
+ false, "DATABASE", SECTION_PRE_DATA,
+ creaQry->data, "", NULL,
+ NULL, 0,
+ NULL, NULL);
+
/*
* pg_largeobject and pg_largeobject_metadata come from the old system
* intact, so set their relfrozenxids and relminmxids.
@@ -2763,11 +2958,19 @@ dumpDatabase(Archive *fout)
PQclear(shres);
}
+ if (default_encoding)
+ free(default_encoding);
+ if (default_collate)
+ free(default_collate);
+ if (default_ctype)
+ free(default_ctype);
+
PQclear(res);
destroyPQExpBuffer(dbQry);
destroyPQExpBuffer(delQry);
destroyPQExpBuffer(creaQry);
+ destroyPQExpBuffer(aclQry);
}
/*
@@ -17709,3 +17912,87 @@ appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
if (!res)
write_msg(NULL, "WARNING: could not parse reloptions array\n");
}
+
+/*
+ * Dump database-specific configuration
+ */
+static void
+dumpDatabaseConfig(Archive *AH, PQExpBuffer creaQry, const char *dbname)
+{
+ PGconn *conn = GetConnection(AH);
+ PQExpBuffer buf = createPQExpBuffer();
+ int count = 1;
+
+ for (;;)
+ {
+ PGresult *res;
+
+ if (AH->remoteVersion >= 90000)
+ printfPQExpBuffer(buf, "SELECT setconfig[%d] FROM pg_db_role_setting WHERE "
+ "setrole = 0 AND setdatabase = (SELECT oid FROM pg_database WHERE datname = ", count);
+ else
+ printfPQExpBuffer(buf, "SELECT datconfig[%d] FROM pg_database WHERE datname = ", count);
+
+ appendStringLiteralConn(buf, dbname, conn);
+
+ if (AH->remoteVersion >= 90000)
+ appendPQExpBuffer(buf, ")");
+
+ res = executeQuery(conn, buf->data);
+ resetPQExpBuffer(buf);
+
+ if (PQntuples(res) == 1 &&
+ !PQgetisnull(res, 0, 0))
+ {
+ makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0),
+ "DATABASE", dbname, NULL, NULL, buf);
+
+ appendPQExpBuffer(creaQry, "%s", buf->data);
+ PQclear(res);
+ count++;
+ }
+ else
+ {
+ PQclear(res);
+ break;
+ }
+ }
+
+ destroyPQExpBuffer(buf);
+}
+
+/*
+ * Dump user-and-database-specific configuration
+ */
+static void
+dumpDbRoleConfig(Archive *AH, PQExpBuffer creaQry)
+{
+ PGconn *conn = GetConnection(AH);
+ PQExpBuffer buf = createPQExpBuffer();
+ PGresult *res;
+ int i;
+
+ printfPQExpBuffer(buf, "SELECT rolname, datname, unnest(setconfig) "
+ "FROM pg_db_role_setting, pg_roles, pg_database "
+ "WHERE setrole = pg_roles.oid AND setdatabase = pg_database.oid");
+ res = executeQuery(conn, buf->data);
+
+ if (PQntuples(res) > 0)
+ {
+ appendPQExpBufferStr(creaQry, "\n\n--\n-- Per-Database Role Settings \n--\n\n");
+
+ resetPQExpBuffer(buf);
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ makeAlterConfigCommand(conn, PQgetvalue(res, i, 2),
+ "ROLE", PQgetvalue(res, i, 0),
+ "DATABASE", PQgetvalue(res, i, 1), buf);
+ }
+
+ appendPQExpBuffer(creaQry, "%s", buf->data);
+ appendPQExpBufferStr(creaQry, "\n\n");
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(buf);
+}
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index d598d10..d2bfbcb 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -38,16 +38,9 @@ static void dumpGroups(PGconn *conn);
static void dropTablespaces(PGconn *conn);
static void dumpTablespaces(PGconn *conn);
static void dropDBs(PGconn *conn);
-static void dumpCreateDB(PGconn *conn);
-static void dumpDatabaseConfig(PGconn *conn, const char *dbname);
static void dumpUserConfig(PGconn *conn, const char *username);
-static void dumpDbRoleConfig(PGconn *conn);
-static void makeAlterConfigCommand(PGconn *conn, const char *arrayitem,
- const char *type, const char *name, const char *type2,
- const char *name2);
static void dumpDatabases(PGconn *conn);
static void dumpTimestamp(const char *msg);
-
static int runPgDump(const char *dbname);
static void buildShSecLabels(PGconn *conn, const char *catalog_name,
uint32 objectId, PQExpBuffer buffer,
@@ -55,15 +48,12 @@ static void buildShSecLabels(PGconn *conn, const char *catalog_name,
static PGconn *connectDatabase(const char *dbname, const char *connstr, const char *pghost, const char *pgport,
const char *pguser, trivalue prompt_password, bool fail_on_error);
static char *constructConnStr(const char **keywords, const char **values);
-static PGresult *executeQuery(PGconn *conn, const char *query);
static void executeCommand(PGconn *conn, const char *query);
static char pg_dump_bin[MAXPGPATH];
-static const char *progname;
static PQExpBuffer pgdumpopts;
static char *connstr = "";
static bool skip_acls = false;
-static bool verbose = false;
static bool dosync = true;
static int binary_upgrade = 0;
@@ -267,7 +257,7 @@ main(int argc, char *argv[])
break;
case 'v':
- verbose = true;
+ g_verbose = true;
appendPQExpBufferStr(pgdumpopts, " -v");
break;
@@ -474,7 +464,7 @@ main(int argc, char *argv[])
executeCommand(conn, "SET quote_all_identifiers = true");
fprintf(OPF, "--\n-- PostgreSQL database cluster dump\n--\n\n");
- if (verbose)
+ if (g_verbose)
dumpTimestamp("Started on");
/*
@@ -534,17 +524,6 @@ main(int argc, char *argv[])
/* Dump tablespaces */
if (!roles_only && !no_tablespaces)
dumpTablespaces(conn);
-
- /* Dump CREATE DATABASE commands */
- if (binary_upgrade || (!globals_only && !roles_only && !tablespaces_only))
- dumpCreateDB(conn);
-
- /* Dump role/database settings */
- if (!tablespaces_only && !roles_only)
- {
- if (server_version >= 90000)
- dumpDbRoleConfig(conn);
- }
}
if (!globals_only && !roles_only && !tablespaces_only)
@@ -552,7 +531,7 @@ main(int argc, char *argv[])
PQfinish(conn);
- if (verbose)
+ if (g_verbose)
dumpTimestamp("Completed on");
fprintf(OPF, "--\n-- PostgreSQL database cluster dump complete\n--\n\n");
@@ -1275,324 +1254,6 @@ dropDBs(PGconn *conn)
}
/*
- * Dump commands to create each database.
- *
- * To minimize the number of reconnections (and possibly ensuing
- * password prompts) required by the output script, we emit all CREATE
- * DATABASE commands during the initial phase of the script, and then
- * run pg_dump for each database to dump the contents of that
- * database. We skip databases marked not datallowconn, since we'd be
- * unable to connect to them anyway (and besides, we don't want to
- * dump template0).
- */
-static void
-dumpCreateDB(PGconn *conn)
-{
- PQExpBuffer buf = createPQExpBuffer();
- char *default_encoding = NULL;
- char *default_collate = NULL;
- char *default_ctype = NULL;
- PGresult *res;
- int i;
-
- fprintf(OPF, "--\n-- Database creation\n--\n\n");
-
- /*
- * First, get the installation's default encoding and locale information.
- * We will dump encoding and locale specifications in the CREATE DATABASE
- * commands for just those databases with values different from defaults.
- *
- * We consider template0's encoding and locale to define the installation
- * default. Pre-8.4 installations do not have per-database locale
- * settings; for them, every database must necessarily be using the
- * installation default, so there's no need to do anything.
- */
- if (server_version >= 80400)
- res = executeQuery(conn,
- "SELECT pg_encoding_to_char(encoding), "
- "datcollate, datctype "
- "FROM pg_database "
- "WHERE datname = 'template0'");
- else
- res = executeQuery(conn,
- "SELECT pg_encoding_to_char(encoding), "
- "null::text AS datcollate, null::text AS datctype "
- "FROM pg_database "
- "WHERE datname = 'template0'");
-
- /* If for some reason the template DB isn't there, treat as unknown */
- if (PQntuples(res) > 0)
- {
- if (!PQgetisnull(res, 0, 0))
- default_encoding = pg_strdup(PQgetvalue(res, 0, 0));
- if (!PQgetisnull(res, 0, 1))
- default_collate = pg_strdup(PQgetvalue(res, 0, 1));
- if (!PQgetisnull(res, 0, 2))
- default_ctype = pg_strdup(PQgetvalue(res, 0, 2));
- }
-
- PQclear(res);
-
-
- /*
- * Now collect all the information about databases to dump.
- *
- * For the database ACLs, as of 9.6, we extract both the positive (as
- * datacl) and negative (as rdatacl) ACLs, relative to the default ACL for
- * databases, which are then passed to buildACLCommands() below.
- *
- * See buildACLQueries() and buildACLCommands().
- *
- * Note that we do not support initial privileges (pg_init_privs) on
- * databases.
- */
- if (server_version >= 90600)
- printfPQExpBuffer(buf,
- "SELECT datname, "
- "coalesce(rolname, (select rolname from %s where oid=(select datdba from pg_database where datname='template0'))), "
- "pg_encoding_to_char(d.encoding), "
- "datcollate, datctype, datfrozenxid, datminmxid, "
- "datistemplate, "
- "(SELECT pg_catalog.array_agg(acl ORDER BY acl::text COLLATE \"C\") FROM ( "
- " SELECT pg_catalog.unnest(coalesce(datacl,pg_catalog.acldefault('d',datdba))) AS acl "
- " EXCEPT SELECT pg_catalog.unnest(pg_catalog.acldefault('d',datdba))) as datacls)"
- "AS datacl, "
- "(SELECT pg_catalog.array_agg(acl ORDER BY acl::text COLLATE \"C\") FROM ( "
- " SELECT pg_catalog.unnest(pg_catalog.acldefault('d',datdba)) AS acl "
- " EXCEPT SELECT pg_catalog.unnest(coalesce(datacl,pg_catalog.acldefault('d',datdba)))) as rdatacls)"
- "AS rdatacl, "
- "datconnlimit, "
- "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
- "FROM pg_database d LEFT JOIN %s u ON (datdba = u.oid) "
- "WHERE datallowconn ORDER BY 1", role_catalog, role_catalog);
- else if (server_version >= 90300)
- printfPQExpBuffer(buf,
- "SELECT datname, "
- "coalesce(rolname, (select rolname from %s where oid=(select datdba from pg_database where datname='template0'))), "
- "pg_encoding_to_char(d.encoding), "
- "datcollate, datctype, datfrozenxid, datminmxid, "
- "datistemplate, datacl, '' as rdatacl, "
- "datconnlimit, "
- "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
- "FROM pg_database d LEFT JOIN %s u ON (datdba = u.oid) "
- "WHERE datallowconn ORDER BY 1", role_catalog, role_catalog);
- else if (server_version >= 80400)
- printfPQExpBuffer(buf,
- "SELECT datname, "
- "coalesce(rolname, (select rolname from %s where oid=(select datdba from pg_database where datname='template0'))), "
- "pg_encoding_to_char(d.encoding), "
- "datcollate, datctype, datfrozenxid, 0 AS datminmxid, "
- "datistemplate, datacl, '' as rdatacl, "
- "datconnlimit, "
- "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
- "FROM pg_database d LEFT JOIN %s u ON (datdba = u.oid) "
- "WHERE datallowconn ORDER BY 1", role_catalog, role_catalog);
- else if (server_version >= 80100)
- printfPQExpBuffer(buf,
- "SELECT datname, "
- "coalesce(rolname, (select rolname from %s where oid=(select datdba from pg_database where datname='template0'))), "
- "pg_encoding_to_char(d.encoding), "
- "null::text AS datcollate, null::text AS datctype, datfrozenxid, 0 AS datminmxid, "
- "datistemplate, datacl, '' as rdatacl, "
- "datconnlimit, "
- "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
- "FROM pg_database d LEFT JOIN %s u ON (datdba = u.oid) "
- "WHERE datallowconn ORDER BY 1", role_catalog, role_catalog);
- else
- printfPQExpBuffer(buf,
- "SELECT datname, "
- "coalesce(usename, (select usename from pg_shadow where usesysid=(select datdba from pg_database where datname='template0'))), "
- "pg_encoding_to_char(d.encoding), "
- "null::text AS datcollate, null::text AS datctype, datfrozenxid, 0 AS datminmxid, "
- "datistemplate, datacl, '' as rdatacl, "
- "-1 as datconnlimit, "
- "(SELECT spcname FROM pg_tablespace t WHERE t.oid = d.dattablespace) AS dattablespace "
- "FROM pg_database d LEFT JOIN pg_shadow u ON (datdba = usesysid) "
- "WHERE datallowconn ORDER BY 1");
-
- res = executeQuery(conn, buf->data);
-
- for (i = 0; i < PQntuples(res); i++)
- {
- char *dbname = PQgetvalue(res, i, 0);
- char *dbowner = PQgetvalue(res, i, 1);
- char *dbencoding = PQgetvalue(res, i, 2);
- char *dbcollate = PQgetvalue(res, i, 3);
- char *dbctype = PQgetvalue(res, i, 4);
- uint32 dbfrozenxid = atooid(PQgetvalue(res, i, 5));
- uint32 dbminmxid = atooid(PQgetvalue(res, i, 6));
- char *dbistemplate = PQgetvalue(res, i, 7);
- char *dbacl = PQgetvalue(res, i, 8);
- char *rdbacl = PQgetvalue(res, i, 9);
- char *dbconnlimit = PQgetvalue(res, i, 10);
- char *dbtablespace = PQgetvalue(res, i, 11);
- char *fdbname;
-
- fdbname = pg_strdup(fmtId(dbname));
-
- resetPQExpBuffer(buf);
-
- /*
- * Skip the CREATE DATABASE commands for "template1" and "postgres",
- * since they are presumably already there in the destination cluster.
- * We do want to emit their ACLs and config options if any, however.
- */
- if (strcmp(dbname, "template1") != 0 &&
- strcmp(dbname, "postgres") != 0)
- {
- appendPQExpBuffer(buf, "CREATE DATABASE %s", fdbname);
-
- appendPQExpBufferStr(buf, " WITH TEMPLATE = template0");
-
- if (strlen(dbowner) != 0)
- appendPQExpBuffer(buf, " OWNER = %s", fmtId(dbowner));
-
- if (default_encoding && strcmp(dbencoding, default_encoding) != 0)
- {
- appendPQExpBufferStr(buf, " ENCODING = ");
- appendStringLiteralConn(buf, dbencoding, conn);
- }
-
- if (default_collate && strcmp(dbcollate, default_collate) != 0)
- {
- appendPQExpBufferStr(buf, " LC_COLLATE = ");
- appendStringLiteralConn(buf, dbcollate, conn);
- }
-
- if (default_ctype && strcmp(dbctype, default_ctype) != 0)
- {
- appendPQExpBufferStr(buf, " LC_CTYPE = ");
- appendStringLiteralConn(buf, dbctype, conn);
- }
-
- /*
- * Output tablespace if it isn't the default. For default, it
- * uses the default from the template database. If tablespace is
- * specified and tablespace creation failed earlier, (e.g. no such
- * directory), the database creation will fail too. One solution
- * would be to use 'SET default_tablespace' like we do in pg_dump
- * for setting non-default database locations.
- */
- if (strcmp(dbtablespace, "pg_default") != 0 && !no_tablespaces)
- appendPQExpBuffer(buf, " TABLESPACE = %s",
- fmtId(dbtablespace));
-
- if (strcmp(dbistemplate, "t") == 0)
- appendPQExpBuffer(buf, " IS_TEMPLATE = true");
-
- if (strcmp(dbconnlimit, "-1") != 0)
- appendPQExpBuffer(buf, " CONNECTION LIMIT = %s",
- dbconnlimit);
-
- appendPQExpBufferStr(buf, ";\n");
- }
- else if (strcmp(dbtablespace, "pg_default") != 0 && !no_tablespaces)
- {
- /*
- * Cannot change tablespace of the database we're connected to, so
- * to move "postgres" to another tablespace, we connect to
- * "template1", and vice versa.
- */
- if (strcmp(dbname, "postgres") == 0)
- appendPQExpBuffer(buf, "\\connect template1\n");
- else
- appendPQExpBuffer(buf, "\\connect postgres\n");
-
- appendPQExpBuffer(buf, "ALTER DATABASE %s SET TABLESPACE %s;\n",
- fdbname, fmtId(dbtablespace));
-
- /* connect to original database */
- appendPsqlMetaConnect(buf, dbname);
- }
-
- if (binary_upgrade)
- {
- appendPQExpBufferStr(buf, "-- For binary upgrade, set datfrozenxid and datminmxid.\n");
- appendPQExpBuffer(buf, "UPDATE pg_catalog.pg_database "
- "SET datfrozenxid = '%u', datminmxid = '%u' "
- "WHERE datname = ",
- dbfrozenxid, dbminmxid);
- appendStringLiteralConn(buf, dbname, conn);
- appendPQExpBufferStr(buf, ";\n");
- }
-
- if (!skip_acls &&
- !buildACLCommands(fdbname, NULL, "DATABASE",
- dbacl, rdbacl, dbowner,
- "", server_version, buf))
- {
- fprintf(stderr, _("%s: could not parse ACL list (%s) for database \"%s\"\n"),
- progname, dbacl, fdbname);
- PQfinish(conn);
- exit_nicely(1);
- }
-
- fprintf(OPF, "%s", buf->data);
-
- dumpDatabaseConfig(conn, dbname);
-
- free(fdbname);
- }
-
- if (default_encoding)
- free(default_encoding);
- if (default_collate)
- free(default_collate);
- if (default_ctype)
- free(default_ctype);
-
- PQclear(res);
- destroyPQExpBuffer(buf);
-
- fprintf(OPF, "\n\n");
-}
-
-
-/*
- * Dump database-specific configuration
- */
-static void
-dumpDatabaseConfig(PGconn *conn, const char *dbname)
-{
- PQExpBuffer buf = createPQExpBuffer();
- int count = 1;
-
- for (;;)
- {
- PGresult *res;
-
- if (server_version >= 90000)
- printfPQExpBuffer(buf, "SELECT setconfig[%d] FROM pg_db_role_setting WHERE "
- "setrole = 0 AND setdatabase = (SELECT oid FROM pg_database WHERE datname = ", count);
- else
- printfPQExpBuffer(buf, "SELECT datconfig[%d] FROM pg_database WHERE datname = ", count);
- appendStringLiteralConn(buf, dbname, conn);
-
- if (server_version >= 90000)
- appendPQExpBuffer(buf, ")");
-
- res = executeQuery(conn, buf->data);
- if (PQntuples(res) == 1 &&
- !PQgetisnull(res, 0, 0))
- {
- makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0),
- "DATABASE", dbname, NULL, NULL);
- PQclear(res);
- count++;
- }
- else
- {
- PQclear(res);
- break;
- }
- }
-
- destroyPQExpBuffer(buf);
-}
-
-
-
-/*
* Dump user-specific configuration
*/
static void
@@ -1618,11 +1279,14 @@ dumpUserConfig(PGconn *conn, const char *username)
appendPQExpBufferChar(buf, ')');
res = executeQuery(conn, buf->data);
+ resetPQExpBuffer(buf);
+
if (PQntuples(res) == 1 &&
!PQgetisnull(res, 0, 0))
{
makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0),
- "ROLE", username, NULL, NULL);
+ "ROLE", username, NULL, NULL, buf);
+ fprintf(OPF, "%s", buf->data);
PQclear(res);
count++;
}
@@ -1636,86 +1300,6 @@ dumpUserConfig(PGconn *conn, const char *username)
destroyPQExpBuffer(buf);
}
-
-/*
- * Dump user-and-database-specific configuration
- */
-static void
-dumpDbRoleConfig(PGconn *conn)
-{
- PQExpBuffer buf = createPQExpBuffer();
- PGresult *res;
- int i;
-
- printfPQExpBuffer(buf, "SELECT rolname, datname, unnest(setconfig) "
- "FROM pg_db_role_setting, %s u, pg_database "
- "WHERE setrole = u.oid AND setdatabase = pg_database.oid", role_catalog);
- res = executeQuery(conn, buf->data);
-
- if (PQntuples(res) > 0)
- {
- fprintf(OPF, "--\n-- Per-Database Role Settings \n--\n\n");
-
- for (i = 0; i < PQntuples(res); i++)
- {
- makeAlterConfigCommand(conn, PQgetvalue(res, i, 2),
- "ROLE", PQgetvalue(res, i, 0),
- "DATABASE", PQgetvalue(res, i, 1));
- }
-
- fprintf(OPF, "\n\n");
- }
-
- PQclear(res);
- destroyPQExpBuffer(buf);
-}
-
-
-/*
- * Helper function for dumpXXXConfig().
- */
-static void
-makeAlterConfigCommand(PGconn *conn, const char *arrayitem,
- const char *type, const char *name,
- const char *type2, const char *name2)
-{
- char *pos;
- char *mine;
- PQExpBuffer buf;
-
- mine = pg_strdup(arrayitem);
- pos = strchr(mine, '=');
- if (pos == NULL)
- {
- free(mine);
- return;
- }
-
- buf = createPQExpBuffer();
-
- *pos = 0;
- appendPQExpBuffer(buf, "ALTER %s %s ", type, fmtId(name));
- if (type2 != NULL && name2 != NULL)
- appendPQExpBuffer(buf, "IN %s %s ", type2, fmtId(name2));
- appendPQExpBuffer(buf, "SET %s TO ", fmtId(mine));
-
- /*
- * Some GUC variable names are 'LIST' type and hence must not be quoted.
- */
- if (pg_strcasecmp(mine, "DateStyle") == 0
- || pg_strcasecmp(mine, "search_path") == 0)
- appendPQExpBufferStr(buf, pos + 1);
- else
- appendStringLiteralConn(buf, pos + 1, conn);
- appendPQExpBufferStr(buf, ";\n");
-
- fprintf(OPF, "%s", buf->data);
- destroyPQExpBuffer(buf);
- free(mine);
-}
-
-
-
/*
* Dump contents of databases.
*/
@@ -1730,29 +1314,11 @@ dumpDatabases(PGconn *conn)
for (i = 0; i < PQntuples(res); i++)
{
int ret;
-
char *dbname = PQgetvalue(res, i, 0);
- PQExpBufferData connectbuf;
- if (verbose)
+ if (g_verbose)
fprintf(stderr, _("%s: dumping database \"%s\"...\n"), progname, dbname);
- initPQExpBuffer(&connectbuf);
- appendPsqlMetaConnect(&connectbuf, dbname);
- fprintf(OPF, "%s\n", connectbuf.data);
- termPQExpBuffer(&connectbuf);
-
- /*
- * Restore will need to write to the target cluster. This connection
- * setting is emitted for pg_dumpall rather than in the code also used
- * by pg_dump, so that a cluster with databases or users which have
- * this flag turned on can still be replicated through pg_dumpall
- * without editing the file or stream. With pg_dump there are many
- * other ways to allow the file to be used, and leaving it out allows
- * users to protect databases from being accidental restore targets.
- */
- fprintf(OPF, "SET default_transaction_read_only = off;\n\n");
-
if (filename)
fclose(OPF);
@@ -1794,6 +1360,13 @@ runPgDump(const char *dbname)
appendPQExpBuffer(cmd, "\"%s\" %s", pg_dump_bin,
pgdumpopts->data);
+
+ /*
+ * The create individual database logic has been moved to pg_dump. invoke
+ * pg_dump with create database option.
+ */
+ appendPQExpBufferStr(cmd, " -C --enable-pgdumpall-behaviour");
+
/*
* If we have a filename, use the undocumented plain-append pg_dump
* format.
@@ -1812,7 +1385,7 @@ runPgDump(const char *dbname)
appendShellString(cmd, connstrbuf->data);
- if (verbose)
+ if (g_verbose)
fprintf(stderr, _("%s: running \"%s\"\n"), progname, cmd->data);
fflush(stdout);
@@ -2105,31 +1678,7 @@ constructConnStr(const char **keywords, const char **values)
return connstr;
}
-/*
- * Run a query, return the results, exit program on failure.
- */
-static PGresult *
-executeQuery(PGconn *conn, const char *query)
-{
- PGresult *res;
- if (verbose)
- fprintf(stderr, _("%s: executing %s\n"), progname, query);
-
- res = PQexec(conn, query);
- if (!res ||
- PQresultStatus(res) != PGRES_TUPLES_OK)
- {
- fprintf(stderr, _("%s: query failed: %s"),
- progname, PQerrorMessage(conn));
- fprintf(stderr, _("%s: query was: %s\n"),
- progname, query);
- PQfinish(conn);
- exit_nicely(1);
- }
-
- return res;
-}
/*
* As above for a SQL command (which returns nothing).
@@ -2139,7 +1688,7 @@ executeCommand(PGconn *conn, const char *query)
{
PGresult *res;
- if (verbose)
+ if (g_verbose)
fprintf(stderr, _("%s: executing %s\n"), progname, query);
res = PQexec(conn, query);
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 3667374..b4fb140 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2521,7 +2521,7 @@ qr/CREATE CAST \(timestamp with time zone AS interval\) WITH FUNCTION pg_catalog
create_order => 47,
create_sql => 'CREATE DATABASE dump_test;',
regexp => qr/^
- \QCREATE DATABASE dump_test WITH TEMPLATE = template0 \E
+ \QCREATE DATABASE dump_test WITH TEMPLATE = template0\E
.*;/xm,
like => { pg_dumpall_dbprivs => 1, },
unlike => {
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 2e621b0..659dd8e 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -62,7 +62,8 @@ generate_old_dump(void)
parallel_exec_prog(log_file_name, NULL,
"\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
- "--binary-upgrade --format=custom %s --file=\"%s\" %s",
+ "--binary-upgrade --create --enable-pgdumpall-behaviour "
+ "--format=custom %s --file=\"%s\" %s",
new_cluster.bindir, cluster_conn_opts(&old_cluster),
log_opts.verbose ? "--verbose" : "",
sql_file_name, escaped_connstr.data);
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index ca1aa5c..5e2a204 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -308,15 +308,6 @@ create_new_objects(void)
char sql_file_name[MAXPGPATH],
log_file_name[MAXPGPATH];
DbInfo *old_db = &old_cluster.dbarr.dbs[dbnum];
- PQExpBufferData connstr,
- escaped_connstr;
-
- initPQExpBuffer(&connstr);
- appendPQExpBuffer(&connstr, "dbname=");
- appendConnStrVal(&connstr, old_db->db_name);
- initPQExpBuffer(&escaped_connstr);
- appendShellString(&escaped_connstr, connstr.data);
- termPQExpBuffer(&connstr);
pg_log(PG_STATUS, "%s", old_db->db_name);
snprintf(sql_file_name, sizeof(sql_file_name), DB_DUMP_FILE_MASK, old_db->db_oid);
@@ -328,13 +319,10 @@ create_new_objects(void)
*/
parallel_exec_prog(log_file_name,
NULL,
- "\"%s/pg_restore\" %s --exit-on-error --verbose --dbname %s \"%s\"",
+ "\"%s/pg_restore\" %s --create --exit-on-error --verbose --dbname template1 \"%s\"",
new_cluster.bindir,
cluster_conn_opts(&new_cluster),
- escaped_connstr.data,
sql_file_name);
-
- termPQExpBuffer(&escaped_connstr);
}
/* reap all children */
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--C94crkcyjafcjHxo
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* [PATCH 2/8] Expand the external data before forming the tuple
@ 2021-03-04 09:25 Dilip Kumar <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Dilip Kumar @ 2021-03-04 09:25 UTC (permalink / raw)
All the callers of HeapTupleGetDatum and HeapTupleHeaderGetDatum, who might
contain the external varlena in their tuple, try to flatten the external
varlena before forming the tuple wherever it is possible.
Dilip Kumar based on idea by Robert Haas
---
src/backend/executor/execExprInterp.c | 29 +++++++++++++++++++++++++--
src/backend/utils/adt/jsonfuncs.c | 8 ++++++--
src/pl/plpgsql/src/pl_exec.c | 5 ++++-
3 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 20949a5dec..c3754acca4 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2840,12 +2840,25 @@ ExecEvalRow(ExprState *state, ExprEvalStep *op)
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < op->d.row.tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(op->d.row.tupdesc, i);
+
+ if (op->d.row.elemnulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.row.elemvalues[i])))
+ continue;
+
+ op->d.row.elemvalues[i] =
+ PointerGetDatum(PG_DETOAST_DATUM_PACKED(op->d.row.elemvalues[i]));
+ }
+
/* build tuple from evaluated field values */
tuple = heap_form_tuple(op->d.row.tupdesc,
op->d.row.elemvalues,
op->d.row.elemnulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
@@ -3085,12 +3098,24 @@ ExecEvalFieldStoreForm(ExprState *state, ExprEvalStep *op, ExprContext *econtext
{
HeapTuple tuple;
+ /* Retrieve externally stored values and decompress. */
+ for (int i = 0; i < (*op->d.fieldstore.argdesc)->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(*op->d.fieldstore.argdesc, i);
+
+ if (op->d.fieldstore.nulls[i] || attr->attlen != -1 ||
+ !VARATT_IS_EXTERNAL(DatumGetPointer(op->d.fieldstore.values[i])))
+ continue;
+ op->d.fieldstore.values[i] = PointerGetDatum(
+ PG_DETOAST_DATUM_PACKED(op->d.fieldstore.values[i]));
+ }
+
/* argdesc should already be valid from the DeForm step */
tuple = heap_form_tuple(*op->d.fieldstore.argdesc,
op->d.fieldstore.values,
op->d.fieldstore.nulls);
- *op->resvalue = HeapTupleGetDatum(tuple);
+ *op->resvalue = HeapTupleGetRawDatum(tuple);
*op->resnull = false;
}
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 511467280f..c3d464f42b 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -2968,7 +2968,7 @@ populate_composite(CompositeIOData *io,
/* populate resulting record tuple */
tuple = populate_record(io->tupdesc, &io->record_io,
defaultval, mcxt, &jso);
- result = HeapTupleHeaderGetDatum(tuple);
+ result = HeapTupleHeaderGetRawDatum(tuple);
JsObjectFree(&jso);
}
@@ -3387,6 +3387,10 @@ populate_record(TupleDesc tupdesc,
nulls[i] ? (Datum) 0 : values[i],
&field,
&nulls[i]);
+
+ if (!nulls[i] && att->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(values[i])))
+ values[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(values[i]));
}
res = heap_form_tuple(tupdesc, values, nulls);
@@ -3765,7 +3769,7 @@ populate_recordset_record(PopulateRecordsetState *state, JsObject *obj)
/* if it's domain over composite, check domain constraints */
if (cache->c.typcat == TYPECAT_COMPOSITE_DOMAIN)
- domain_check(HeapTupleHeaderGetDatum(tuphead), false,
+ domain_check(HeapTupleHeaderGetRawDatum(tuphead), false,
cache->argtype,
&cache->c.io.composite.domain_info,
cache->fn_mcxt);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index b4c70aaa7f..fd073767bc 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -5326,7 +5326,7 @@ exec_eval_datum(PLpgSQL_execstate *estate,
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
- *value = HeapTupleGetDatum(tup);
+ *value = HeapTupleGetRawDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext);
break;
@@ -7300,6 +7300,9 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
+ if (!nulls[i] && TupleDescAttr(tupdesc, i)->attlen == -1 &&
+ VARATT_IS_EXTERNAL(DatumGetPointer(dvalues[i])))
+ dvalues[i] = PointerGetDatum(PG_DETOAST_DATUM_PACKED(dvalues[i]));
/* XXX should we insist on typmod match, too? */
}
--
2.17.0
--cvVnyQ+4j833TQvp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0003-Disallow-compressed-data-inside-container-types.patch"
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-02-25 11:53 Álvaro Herrera <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Álvaro Herrera @ 2026-02-25 11:53 UTC (permalink / raw)
To: Amul Sul <[email protected]>; +Cc: Akshay Joshi <[email protected]>; Andrew Dunstan <[email protected]>; Euler Taveira <[email protected]>; Chao Li <[email protected]>; japin <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On 2026-Feb-24, Amul Sul wrote:
> On Mon, Feb 23, 2026 at 7:27 PM Akshay Joshi
> <[email protected]> wrote:
> >
> > On Mon, Feb 23, 2026 at 6:50 PM Amul Sul <[email protected]> wrote:
> > >
> > > + /* 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))
> > >
> > >
> > > Comment doesn't quite match the logic in the condition.
> >
> > Will update this in my next patch. When we decide which approach to follow:
> > 1) Double Dash:
> > v8-0001-Add-pg_get_database_ddl-function-to-reconstruct-double-dash.patch
> > 2) DefElem (Key-Value):
> > v8-0002-Add-pg_get_database_ddl-function-to-reconstruct-DefElem.patch
>
> That’s a bit subjective, as different people will likely have
> different opinions. I prefer the first version without the -- prefix,
> since the counterpart would be the default behavior. For example, if
> "no-tablespace" is not specified, the tablespace would be included in
> the DDL dump by default.
I'm surprised to not have seen an update on this topic following the
discovery by Mark Wong that commit d32d1463995c (in branch 18) already
established a convention for passing arguments to functions: use argument
pairs to variadic functions, the way pg_restore_relation_stats() and
pg_restore_attribute_stats() work. While I like my previous suggestion
of using DefElems better, I think it's more sensible to follow this
established precedent and not innovate on this.
Regards
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-02-25 21:19 Euler Taveira <[email protected]>
parent: Álvaro Herrera <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Euler Taveira @ 2026-02-25 21:19 UTC (permalink / raw)
To: Álvaro Herrera <[email protected]>; Amul Sul <[email protected]>; +Cc: Akshay Joshi <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; japin <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Wed, Feb 25, 2026, at 8:53 AM, Álvaro Herrera wrote:
>
> I'm surprised to not have seen an update on this topic following the
> discovery by Mark Wong that commit d32d1463995c (in branch 18) already
> established a convention for passing arguments to functions: use argument
> pairs to variadic functions, the way pg_restore_relation_stats() and
> pg_restore_attribute_stats() work. While I like my previous suggestion
> of using DefElems better, I think it's more sensible to follow this
> established precedent and not innovate on this.
>
This convention is much older than the referred commit. It predates from the
logical decoding (commit b89e151054a0). See pg_logical_slot_get_changes_guts()
that is an internal function for pg_logical_slot_FOO_changes(). It seems a
good idea to have a central function to validate the variadic parameter for all
of these functions.
--
Euler Taveira
EDB https://www.enterprisedb.com/
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-02-27 12:22 Akshay Joshi <[email protected]>
parent: Euler Taveira <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-02-27 12:22 UTC (permalink / raw)
To: Euler Taveira <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; japin <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
Following suggestions from Alvaro and Mark, I have re-implemented this
feature using variadic function argument pairs.
This patch incorporates Mark Wong’s documentation updates, Amul’s one
review comment, and the majority of Euler’s comments.
*New changes:*
- Supports flexible DDL options as key-value pairs:
- pretty - Format output for readability
- owner - Include OWNER clause
- tablespace - Include TABLESPACE clause
- defaults - Include parameters even if it is set to default value.
- Option values accept: 'yes'/'on'/true/'1' (or their negatives)
*Usage Examples:*
-- Basic usage with options
SELECT pg_get_database_ddl('mydb', 'owner', 'yes', 'defaults', 'yes');
-- Pretty-printed output without owner and tablespace
SELECT pg_get_database_ddl('mydb', 'owner', 'no', 'tablespace', 'no',
'pretty', 'on');
-- Using boolean values
SELECT pg_get_database_ddl('mydb', 'owner', false, 'defaults', true);
-- Using OID
SELECT pg_get_database_ddl(16384, 'pretty', 'yes');
*Note:* To keep things clean, I’ve moved the logic into two generic
functions (*get_formatted_string* and *parse_ddl_options*) and a common
*DDLOptionDef* struct. This should simplify the work for the rest of the
pg_get_<object>_ddl patches.
Attached is the v9 patch which is ready for review.
On Thu, Feb 26, 2026 at 2:49 AM Euler Taveira <[email protected]> wrote:
> On Wed, Feb 25, 2026, at 8:53 AM, Álvaro Herrera wrote:
> >
> > I'm surprised to not have seen an update on this topic following the
> > discovery by Mark Wong that commit d32d1463995c (in branch 18) already
> > established a convention for passing arguments to functions: use argument
> > pairs to variadic functions, the way pg_restore_relation_stats() and
> > pg_restore_attribute_stats() work. While I like my previous suggestion
> > of using DefElems better, I think it's more sensible to follow this
> > established precedent and not innovate on this.
> >
>
> This convention is much older than the referred commit. It predates from
> the
> logical decoding (commit b89e151054a0). See
> pg_logical_slot_get_changes_guts()
> that is an internal function for pg_logical_slot_FOO_changes(). It seems a
> good idea to have a central function to validate the variadic parameter
> for all
> of these functions.
>
>
> --
> Euler Taveira
> EDB https://www.enterprisedb.com/
>
Attachments:
[application/octet-stream] v9-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (32.8K, ../../CANxoLDfR-8nOYetoSKiJ9yVvMkXodVRqR+e=7CSEwiJTrU7yrA@mail.gmail.com/3-v9-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From 8c1e39cbae52a44ece8dd03b74663892222f743a Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Wed, 24 Sep 2025 17:47:59 +0530
Subject: [PATCH v9] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 86 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 397 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 39 +++
src/test/regress/expected/database.out | 170 ++++++++++
src/test/regress/sql/database.sql | 108 ++++++
7 files changed, 813 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 294f45e82a3..c346b8d1fcf 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,90 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> statement from the
+ system catalogs for a specified database by name or OID. The optional
+ variadic arguments are name/value pairs to control the output
+ formatting and content (e.g., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Adds newlines and indentation for better readability.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 69699f8830a..ae573e2fb2c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -378,6 +378,12 @@ BEGIN ATOMIC
END;
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+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 f16f1535785..988d9d2b36a 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,45 @@
#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
+
+/*
+ * 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) */
+ uint64 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint64 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,358 @@ 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 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 uint64
+parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
+{
+ uint64 flags = 0;
+ Datum *args;
+ bool *nulls;
+ Oid *types;
+ int nargs;
+ int i;
+
+ /* 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;
+
+ /* No arguments provided */
+ if (nargs == 0)
+ return flags;
+
+ /* Arguments must come in name/value pairs */
+ if (nargs % 2 != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument list must have even number of elements"),
+ errhint("The arguments of %s must consist of alternating keys and values.",
+ "pg_get_database_ddl()")));
+
+ for (i = 0; i < nargs; i += 2)
+ {
+ char *name;
+ bool bval;
+
+ /* Key must not be null */
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: key must not be null", i + 1)));
+
+ /* Key must be text type */
+ if (types[i] != TEXTOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: key must be text type", i + 1)));
+
+ name = TextDatumGetCString(args[i]);
+
+ /* Value must not be null */
+ if (nulls[i + 1])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value must not be null for key \"%s\"",
+ i + 2, 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("argument %d: invalid value \"%s\" for key \"%s\"",
+ i + 2, valstr, name),
+ errhint("Valid values are: true, false, yes, no, 1, 0, on, off.")));
+ pfree(valstr);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value for key \"%s\" must be boolean or text type",
+ i + 2, name)));
+ }
+
+ /*
+ * Look up the option in the ddl_option_defs array and set the
+ * appropriate flag based on the value.
+ */
+ {
+ bool found = false;
+ int j;
+
+ for (j = 0; j < lengthof(ddl_option_defs); j++)
+ {
+ const DDLOptionDef *opt = &ddl_option_defs[j];
+
+ if (pg_strcasecmp(name, opt->name) == 0)
+ {
+ /*
+ * 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 name or oid.
+ *
+ * db_oid - OID/Name 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 = PG_GETARG_OID(0);
+ uint64 ddl_flags;
+ char *res;
+
+ /* Parse variadic options starting from argument 1 */
+ ddl_flags = parse_ddl_options(fcinfo, 1);
+
+ res = pg_get_database_ddl_worker(db_oid, ddl_flags);
+
+ if (res == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(string_to_text(res));
+}
+
+static char *
+pg_get_database_ddl_worker(Oid db_oid, uint64 ddl_flags)
+{
+ 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;
+ 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) ? 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 owner is not omitted */
+ 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 is_with_defaults is true, then 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 tablespace is not omitted */
+ 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 dac40992cbc..5c43eee32ec 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4031,6 +4031,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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 <stdbool.h>
+
+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..5b4aa6c054d 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,122 @@ 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 options
+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', false));
+ 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', true, 'tablespace', false));
+ 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', true));
+ 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', false, 'tablespace', false, 'defaults', true));
+ 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', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+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', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ENCODING = 'UTF8'
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+---------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123;
+(1 row)
+
+-- Using '1' and '0'
+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)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+---------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123;
+(1 row)
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: argument 2: invalid value "invalid" for key "owner"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
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..888d1e5283f 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,58 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-04 08:39 Japin Li <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Japin Li @ 2026-03-04 08:39 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Euler Taveira <[email protected]>; Álvaro Herrera <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Fri, 27 Feb 2026 at 17:52, Akshay Joshi <[email protected]> wrote:
> Following suggestions from Alvaro and Mark, I have re-implemented this feature using variadic
> function argument pairs.
> This patch incorporates Mark Wong’s documentation updates, Amul’s one review comment, and the
> majority of Euler’s comments.
>
> New changes:
> - Supports flexible DDL options as key-value pairs:
> - pretty - Format output for readability
> - owner - Include OWNER clause
> - tablespace - Include TABLESPACE clause
> - defaults - Include parameters even if it is set to default value.
> - Option values accept: 'yes'/'on'/true/'1' (or their negatives)
>
> Usage Examples:
> -- Basic usage with options
> SELECT pg_get_database_ddl('mydb', 'owner', 'yes', 'defaults', 'yes');
> -- Pretty-printed output without owner and tablespace
> SELECT pg_get_database_ddl('mydb', 'owner', 'no', 'tablespace', 'no', 'pretty', 'on');
> -- Using boolean values
> SELECT pg_get_database_ddl('mydb', 'owner', false, 'defaults', true);
> -- Using OID
> SELECT pg_get_database_ddl(16384, 'pretty', 'yes');
>
> Note: To keep things clean, I’ve moved the logic into two generic functions (get_formatted_string
> and parse_ddl_options) and a common DDLOptionDef struct. This should simplify the work for the
> rest of the pg_get_<object>_ddl patches.
>
> Attached is the v9 patch which is ready for review.
>
> On Thu, Feb 26, 2026 at 2:49 AM Euler Taveira <[email protected]> wrote:
>
> On Wed, Feb 25, 2026, at 8:53 AM, Álvaro Herrera wrote:
> >
> > I'm surprised to not have seen an update on this topic following the
> > discovery by Mark Wong that commit d32d1463995c (in branch 18) already
> > established a convention for passing arguments to functions: use argument
> > pairs to variadic functions, the way pg_restore_relation_stats() and
> > pg_restore_attribute_stats() work. While I like my previous suggestion
> > of using DefElems better, I think it's more sensible to follow this
> > established precedent and not innovate on this.
> >
>
> This convention is much older than the referred commit. It predates from the
> logical decoding (commit b89e151054a0). See pg_logical_slot_get_changes_guts()
> that is an internal function for pg_logical_slot_FOO_changes(). It seems a
> good idea to have a central function to validate the variadic parameter for all
> of these functions.
>
Thanks for updating the patch, here are some comments on v9.
1.
+ uint64 flag; /* Flag to set */
Do we actually need 64 bits for this flag field?
2.
+ /* Indent with spaces */
+ for (int i = 0; i < nSpaces; i++)
+ {
+ appendStringInfoChar(buf, ' ');
+ }
How about using appendStringInfoSpaces(buf, nSpaces) instead?
3.
+ /* If no options provided (VARIADIC NULL), return the empty bitmask */
+ if (nargs < 0)
+ return flags;
+
+ ...
+
+ /* No arguments provided */
+ if (nargs == 0)
+ return flags;
These two conditions are identical — how about just `if (nargs <= 0)`?
4.
+ /* Arguments must come in name/value pairs */
+ if (nargs % 2 != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument list must have even number of elements"),
+ errhint("The arguments of %s must consist of alternating keys and values.",
+ "pg_get_database_ddl()")));
Should we align this with stats_fill_fcinfo_from_arg_pairs()?
Suggested wording:
errmsg("variadic arguments must be name/value pairs")
5.
+ /* Key must not be null */
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: key must not be null", i + 1)));
+
Suggested wording:
errmsg("name at variadic position %d is null")
6.
+ /* Key must be text type */
+ if (types[i] != TEXTOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: key must be text type", i + 1)));
Suggested wording:
errmsg("name at variadic position %d has type %s, expected type %s")
7.
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value for key \"%s\" must be boolean or text type",
+ i + 2, name)));
Suggested wording:
errmsg("argument \"%s\" has type %s, execpted type boolean or text")
See stats_check_arg_type().
8.
+ for (i = 0; i < nargs; i += 2)
+ {
We can narrow the scope of `i` by declaring it in the for initializer.
9.
+ {
+ bool found = false;
+ int j;
+
+ for (j = 0; j < lengthof(ddl_option_defs); j++)
+ {
Minor style improvements:
- We can (and should) declare `j` inside its for-loop initializer, just like `i`.
- Move the declaration of `found` up to the top of the outer for-loop scope.
This allows us to remove the unnecessary braces around the loop body.
--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-04 09:30 Japin Li <[email protected]>
parent: Japin Li <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Japin Li @ 2026-03-04 09:30 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Euler Taveira <[email protected]>; Álvaro Herrera <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Wed, 04 Mar 2026 at 16:39, Japin Li <[email protected]> wrote:
> On Fri, 27 Feb 2026 at 17:52, Akshay Joshi <[email protected]> wrote:
>> Following suggestions from Alvaro and Mark, I have re-implemented this feature using variadic
>> function argument pairs.
>> This patch incorporates Mark Wong’s documentation updates, Amul’s one review comment, and the
>> majority of Euler’s comments.
>>
>> New changes:
>> - Supports flexible DDL options as key-value pairs:
>> - pretty - Format output for readability
>> - owner - Include OWNER clause
>> - tablespace - Include TABLESPACE clause
>> - defaults - Include parameters even if it is set to default value.
>> - Option values accept: 'yes'/'on'/true/'1' (or their negatives)
>>
>> Usage Examples:
>> -- Basic usage with options
>> SELECT pg_get_database_ddl('mydb', 'owner', 'yes', 'defaults', 'yes');
>> -- Pretty-printed output without owner and tablespace
>> SELECT pg_get_database_ddl('mydb', 'owner', 'no', 'tablespace', 'no', 'pretty', 'on');
>> -- Using boolean values
>> SELECT pg_get_database_ddl('mydb', 'owner', false, 'defaults', true);
>> -- Using OID
>> SELECT pg_get_database_ddl(16384, 'pretty', 'yes');
>>
>> Note: To keep things clean, I’ve moved the logic into two generic functions (get_formatted_string
>> and parse_ddl_options) and a common DDLOptionDef struct. This should simplify the work for the
>> rest of the pg_get_<object>_ddl patches.
>>
>> Attached is the v9 patch which is ready for review.
>>
>> On Thu, Feb 26, 2026 at 2:49 AM Euler Taveira <[email protected]> wrote:
>>
>> On Wed, Feb 25, 2026, at 8:53 AM, Álvaro Herrera wrote:
>> >
>> > I'm surprised to not have seen an update on this topic following the
>> > discovery by Mark Wong that commit d32d1463995c (in branch 18) already
>> > established a convention for passing arguments to functions: use argument
>> > pairs to variadic functions, the way pg_restore_relation_stats() and
>> > pg_restore_attribute_stats() work. While I like my previous suggestion
>> > of using DefElems better, I think it's more sensible to follow this
>> > established precedent and not innovate on this.
>> >
>>
>> This convention is much older than the referred commit. It predates from the
>> logical decoding (commit b89e151054a0). See pg_logical_slot_get_changes_guts()
>> that is an internal function for pg_logical_slot_FOO_changes(). It seems a
>> good idea to have a central function to validate the variadic parameter for all
>> of these functions.
>>
>
> Thanks for updating the patch, here are some comments on v9.
>
> 1.
> + uint64 flag; /* Flag to set */
>
> Do we actually need 64 bits for this flag field?
>
> 2.
> + /* Indent with spaces */
> + for (int i = 0; i < nSpaces; i++)
> + {
> + appendStringInfoChar(buf, ' ');
> + }
>
> How about using appendStringInfoSpaces(buf, nSpaces) instead?
>
> 3.
> + /* If no options provided (VARIADIC NULL), return the empty bitmask */
> + if (nargs < 0)
> + return flags;
> +
> + ...
> +
> + /* No arguments provided */
> + if (nargs == 0)
> + return flags;
>
> These two conditions are identical — how about just `if (nargs <= 0)`?
>
> 4.
> + /* Arguments must come in name/value pairs */
> + if (nargs % 2 != 0)
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("argument list must have even number of elements"),
> + errhint("The arguments of %s must consist of alternating keys and values.",
> + "pg_get_database_ddl()")));
>
> Should we align this with stats_fill_fcinfo_from_arg_pairs()?
>
> Suggested wording:
> errmsg("variadic arguments must be name/value pairs")
>
> 5.
> + /* Key must not be null */
> + if (nulls[i])
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("argument %d: key must not be null", i + 1)));
> +
>
> Suggested wording:
>
> errmsg("name at variadic position %d is null")
>
> 6.
> + /* Key must be text type */
> + if (types[i] != TEXTOID)
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("argument %d: key must be text type", i + 1)));
>
> Suggested wording:
>
> errmsg("name at variadic position %d has type %s, expected type %s")
>
> 7.
> + ereport(ERROR,
> + (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> + errmsg("argument %d: value for key \"%s\" must be boolean or text type",
> + i + 2, name)));
>
> Suggested wording:
>
> errmsg("argument \"%s\" has type %s, execpted type boolean or text")
>
> See stats_check_arg_type().
>
> 8.
> + for (i = 0; i < nargs; i += 2)
> + {
>
> We can narrow the scope of `i` by declaring it in the for initializer.
>
> 9.
> + {
> + bool found = false;
> + int j;
> +
> + for (j = 0; j < lengthof(ddl_option_defs); j++)
> + {
>
> Minor style improvements:
>
> - We can (and should) declare `j` inside its for-loop initializer, just like `i`.
> - Move the declaration of `found` up to the top of the outer for-loop scope.
> This allows us to remove the unnecessary braces around the loop body.
>
After playing with this patch, I’m seeing the following output:
# select pg_get_database_ddl('postgres'::regdatabase, 'defaults', true, 'pretty', true, 'pretty', false);
pg_get_database_ddl
------------------------------------
CREATE DATABASE postgres +
WITH +
OWNER = japin +
ENCODING = 'UTF8' +
LC_COLLATE = 'en_US.UTF-8'+
LC_CTYPE = 'en_US.UTF-8' +
COLLATION_VERSION = '2.39'+
LOCALE_PROVIDER = libc +
TABLESPACE = pg_default +
ALLOW_CONNECTIONS = true +
CONNECTION LIMIT = -1;
(1 row)
Is this the expected behavior?
--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-04 12:59 Akshay Joshi <[email protected]>
parent: Japin Li <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-04 12:59 UTC (permalink / raw)
To: Álvaro Herrera <[email protected]>; +Cc: Japin Li <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
Thanks for the review, Japin. I’ve addressed all of your comments. I also
added a check to throw an error if an option appears more than once.
Attached is the *v10 patch*, now ready for further review.
On Wed, Mar 4, 2026 at 3:15 PM Álvaro Herrera <[email protected]> wrote:
> On 2026-Mar-04, Japin Li wrote:
>
> > After playing with this patch, I’m seeing the following output:
> >
> > # select pg_get_database_ddl('postgres'::regdatabase, 'defaults', true,
> 'pretty', true, 'pretty', false);
>
> I think this should throw an error that 'pretty' was contradictorily
> specified twice. (Maybe also an error if an option appears twice,
> period.)
>
> --
> Álvaro Herrera 48°01'N 7°57'E —
> https://www.EnterpriseDB.com/
>
Attachments:
[application/octet-stream] v10-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (33.3K, ../../CANxoLDcW2iyOq8JJYQXkzXkK6PtQX-z7Hxe=Ri3nQJX6AaPLjw@mail.gmail.com/3-v10-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From 0a3b5111bcf19509807719aaa1bedf72d4d56670 Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Wed, 24 Sep 2025 17:47:59 +0530
Subject: [PATCH v10] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 86 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 395 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 39 +++
src/test/regress/expected/database.out | 173 ++++++++++
src/test/regress/sql/database.sql | 111 +++++++
7 files changed, 817 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 294f45e82a3..c346b8d1fcf 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,90 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> statement from the
+ system catalogs for a specified database by name or OID. The optional
+ variadic arguments are name/value pairs to control the output
+ formatting and content (e.g., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Adds newlines and indentation for better readability.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 69699f8830a..ae573e2fb2c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -378,6 +378,12 @@ BEGIN ATOMIC
END;
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+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 f16f1535785..d473ae75298 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,45 @@
#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
+
+/*
+ * 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint32 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,356 @@ 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 */
+ appendStringInfoSpaces(buf, nSpaces);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ va_start(args, fmt);
+ appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+}
+
+/**
+ * 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;
+ bool found = false;
+
+ /* 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;
+
+ /* 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("argument \"%s\" has type %s, expected type boolean or text",
+ name, format_type_be(types[i + 1]))));
+
+ /* 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("argument %d: invalid value \"%s\" for key \"%s\"",
+ i + 2, valstr, name),
+ errhint("Valid values are: true, false, yes, no, 1, 0, on, off.")));
+ pfree(valstr);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value for key \"%s\" must be boolean or text type",
+ i + 2, name)));
+ }
+
+ /*
+ * 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 name or oid.
+ *
+ * db_oid - OID/Name 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 = PG_GETARG_OID(0);
+ uint32 ddl_flags;
+ char *res;
+
+ /* Parse variadic options starting from argument 1 */
+ ddl_flags = parse_ddl_options(fcinfo, 1);
+
+ res = pg_get_database_ddl_worker(db_oid, ddl_flags);
+
+ if (res == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(string_to_text(res));
+}
+
+static char *
+pg_get_database_ddl_worker(Oid db_oid, uint32 ddl_flags)
+{
+ 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;
+ 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) ? 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 owner is not omitted */
+ 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 is_with_defaults is true, then 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 tablespace is not omitted */
+ 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 dac40992cbc..5c43eee32ec 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4031,6 +4031,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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 <stdbool.h>
+
+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..77235949f66 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,125 @@ 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 options
+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', false));
+ 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', true, 'tablespace', false));
+ 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', true));
+ 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', false, 'tablespace', false, 'defaults', true));
+ 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', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+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', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ENCODING = 'UTF8'
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+---------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123;
+(1 row)
+
+-- Using '1' and '0'
+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)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+---------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: argument 2: invalid value "invalid" for key "owner"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
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..05fd94ab9b4 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,61 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-04 14:01 Japin Li <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Japin Li @ 2026-03-04 14:01 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <[email protected]> wrote:
> Thanks for the review, Japin. I’ve addressed all of your comments. I also added a check to throw an error if an option
> appears more than once.
>
> Attached is the v10 patch, now ready for further review.
>
Thanks for updating the patch. Here are some comments on v10.
1.
+ * db_oid - OID/Name of the database for which to generate the DDL.
Should the comment be updated? The code only accepts an OID for `db_oid`,
database names are not supported.
2.
+ /* 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",
+ quote_identifier(dbowner));
+ }
`dbowner` is only needed inside this `if` — how about declaring it there to
reduce its scope?
3.
+ /* If is_with_defaults is true, then 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)));
+ }
How about cache the result of `pg_encoding_to_char()` in a local variable to
avoid calling it twice?
--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-05 09:50 Akshay Joshi <[email protected]>
parent: Japin Li <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-05 09:50 UTC (permalink / raw)
To: Japin Li <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
I’ve addressed all of your comments.
Attached is the *v11 patch*, now ready for further review.
On Wed, Mar 4, 2026 at 7:31 PM Japin Li <[email protected]> wrote:
> On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <[email protected]>
> wrote:
> > Thanks for the review, Japin. I’ve addressed all of your comments. I
> also added a check to throw an error if an option
> > appears more than once.
> >
> > Attached is the v10 patch, now ready for further review.
> >
>
> Thanks for updating the patch. Here are some comments on v10.
>
> 1.
> + * db_oid - OID/Name of the database for which to generate the DDL.
>
> Should the comment be updated? The code only accepts an OID for `db_oid`,
> database names are not supported.
>
> 2.
> + /* 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",
> +
> quote_identifier(dbowner));
> + }
>
> `dbowner` is only needed inside this `if` — how about declaring it there to
> reduce its scope?
>
> 3.
> + /* If is_with_defaults is true, then 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)));
> + }
>
> How about cache the result of `pg_encoding_to_char()` in a local variable
> to
> avoid calling it twice?
>
> --
> Regards,
> Japin Li
> ChengDu WenWu Information Technology Co., Ltd.
>
Attachments:
[application/octet-stream] v11-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (33.7K, ../../CANxoLDdNsuXxL7yLgqWNaR=VCjqMgJ77oU3OvJ6KwBVKi7NVEw@mail.gmail.com/3-v11-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From 90c521d74cfa5e45d5174c4ea6f2d192f061d1c3 Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Wed, 24 Sep 2025 17:47:59 +0530
Subject: [PATCH v11] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 86 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 403 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 37 +++
src/test/regress/expected/database.out | 180 ++++++++++
src/test/regress/sql/database.sql | 112 +++++++
7 files changed, 831 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 294f45e82a3..c346b8d1fcf 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,90 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> statement from the
+ system catalogs for a specified database by name or OID. The optional
+ variadic arguments are name/value pairs to control the output
+ formatting and content (e.g., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Adds newlines and indentation for better readability.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 69699f8830a..ae573e2fb2c 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -378,6 +378,12 @@ BEGIN ATOMIC
END;
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+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 f16f1535785..bbf82c1d6c0 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,45 @@
#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
+
+/*
+ * 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint32 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,364 @@ 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 */
+ appendStringInfoSpaces(buf, nSpaces);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ for (;;)
+ {
+ int needed;
+
+ va_start(args, fmt);
+ needed = appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(buf, needed);
+ }
+}
+
+/**
+ * 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;
+ bool found = false;
+
+ /* 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;
+
+ 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("argument %d: invalid value \"%s\" for key \"%s\"",
+ i + 2, valstr, name),
+ errhint("Valid values are: true, false, yes, no, 1, 0, on, off.")));
+ pfree(valstr);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value for key \"%s\" must be boolean or text type",
+ i + 2, name)));
+ }
+
+ /*
+ * 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 name or 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(string_to_text(res));
+}
+
+static char *
+pg_get_database_ddl_worker(Oid db_oid, uint32 ddl_flags)
+{
+ const char *encoding = 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;
+ 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) ? 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);
+
+ /* 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 owner is not omitted */
+ if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
+ {
+ char *dbowner = GetUserNameFromId(dbform->datdba, false);
+
+ if (dbowner != NULL)
+ {
+ get_formatted_string(&buf, pretty_flags, 8, "OWNER = %s",
+ quote_identifier(dbowner));
+ }
+ }
+
+ /* Set the ENCODING in the DDL */
+ encoding = pg_encoding_to_char(dbform->encoding);
+
+ if (is_with_defaults ||
+ pg_strcasecmp(encoding, DDL_DEFAULTS.DATABASE.ENCODING) != 0)
+ {
+ get_formatted_string(&buf, pretty_flags, 8, "ENCODING = %s",
+ quote_literal_cstr(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 tablespace is not omitted */
+ 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 (is_with_defaults || 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 dac40992cbc..c8f3736392d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4031,6 +4031,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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..9f21c42c05a
--- /dev/null
+++ b/src/include/utils/ddl_defaults.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+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..5d2834771ac 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,132 @@ 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);
+ pg_get_database_ddl
+---------------------
+
+(1 row)
+
+-- Without options
+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', false));
+ 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', true, 'tablespace', false));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ OWNER = regress_datdba_after
+ ENCODING = 'UTF8'
+ TABLESPACE = pg_default
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ENCODING = 'UTF8'
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using '1' and '0'
+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 IS_TEMPLATE = false;
+(1 row)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: argument 2: invalid value "invalid" for key "owner"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
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..b67b56006d8 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,62 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-05 14:45 Rafia Sabih <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Rafia Sabih @ 2026-03-05 14:45 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Japin Li <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
I like the idea for this patch and find this useful.
I have few comments for the patch, looking at the test cases added in the
patch, it is not clear to me what is the default value for each of the
options. For example, in
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true,
'tablespace', false));
+
ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
IS_TEMPLATE = false;
+(1 row)
+
owner option is omitted but the output contains the owner information. So
does it mean that the default value for each option is true?
But that doesn't seem so, because omitting pretty option, doesn't give
output in pretty, rather pretty is used only when provided.
I find this rather confusing, it is worth documenting what is the default
value for each of these options.
Similarly, what is the expected behaviour when defaults option is not
provided,
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+ 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', true,
'tablespace', false));
+
ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
IS_TEMPLATE = false;
+(1 row)
Why is connection_limit present in both of these cases...?
Another thing, what is the significance of having defaults option, because
I think that knowing non-default values could be more useful, or atleast
there should be a way to know the non-default options for the database.
Also, the option strategy is missing in the output, is it deliberate? If
yes, why?
On Thu, 5 Mar 2026 at 01:50, Akshay Joshi <[email protected]>
wrote:
> I’ve addressed all of your comments.
>
> Attached is the *v11 patch*, now ready for further review.
>
> On Wed, Mar 4, 2026 at 7:31 PM Japin Li <[email protected]> wrote:
>
>> On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <[email protected]>
>> wrote:
>> > Thanks for the review, Japin. I’ve addressed all of your comments. I
>> also added a check to throw an error if an option
>> > appears more than once.
>> >
>> > Attached is the v10 patch, now ready for further review.
>> >
>>
>> Thanks for updating the patch. Here are some comments on v10.
>>
>> 1.
>> + * db_oid - OID/Name of the database for which to generate the DDL.
>>
>> Should the comment be updated? The code only accepts an OID for `db_oid`,
>> database names are not supported.
>>
>> 2.
>> + /* 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",
>> +
>> quote_identifier(dbowner));
>> + }
>>
>> `dbowner` is only needed inside this `if` — how about declaring it there
>> to
>> reduce its scope?
>>
>> 3.
>> + /* If is_with_defaults is true, then 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)));
>> + }
>>
>> How about cache the result of `pg_encoding_to_char()` in a local variable
>> to
>> avoid calling it twice?
>>
>> --
>> Regards,
>> Japin Li
>> ChengDu WenWu Information Technology Co., Ltd.
>>
>
--
Regards,
Rafia Sabih
CYBERTEC PostgreSQL International GmbH
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-05 16:15 Akshay Joshi <[email protected]>
parent: Rafia Sabih <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-05 16:15 UTC (permalink / raw)
To: Rafia Sabih <[email protected]>; +Cc: Japin Li <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Thu, Mar 5, 2026 at 8:15 PM Rafia Sabih <[email protected]>
wrote:
> I like the idea for this patch and find this useful.
>
> I have few comments for the patch, looking at the test cases added in the
> patch, it is not clear to me what is the default value for each of the
> options. For example, in
>
Following the postgresql documentation, I created ddl_defaults.h to
handle default options. This will be used across all database objects in
future pg_get_<object>_ddl patches.
> +-- With No Tablespace
> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults',
> true, 'tablespace', false));
> +
> ddl_filter
>
>
> +----------------------------------------------------------------------------------------------------------------------------------------------------------
> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
> IS_TEMPLATE = false;
> +(1 row)
> +
> owner option is omitted but the output contains the owner information. So
> does it mean that the default value for each option is true?
> But that doesn't seem so, because omitting pretty option, doesn't give
> output in pretty, rather pretty is used only when provided.
> I find this rather confusing, it is worth documenting what is the default
> value for each of these options.
>
In my view, every database has an owner; it's a required attribute, not
an optional one with a "default" value. The OWNER clause is correctly
controlled only by the PG_DDL_NO_OWNER flag. Users must explicitly
provide (*'owner',
false/'no'/'off')* to opt out of including ownership info in the DDL. When
comparing the Owner and Pretty options: by default, the Owner is included
in the DDL reconstruction, whereas the Pretty option is set to false by
default.
In my initial patches, I used flags like --no-owner and --no-tablespace
because they are very intuitive for users. However, reviewers noted that
this style is unique to pg_dump and not used elsewhere. Consequently, I
switched to the existing name-value pair format using VARIADIC arguments.
>
> Similarly, what is the expected behaviour when defaults option is not
> provided,
> +-- With No Owner
> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
> + 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',
> true, 'tablespace', false));
> +
> ddl_filter
>
>
> +----------------------------------------------------------------------------------------------------------------------------------------------------------
> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
> IS_TEMPLATE = false;
> +(1 row)
> Why is connection_limit present in both of these cases...?
>
> Another thing, what is the significance of having defaults option, because
> I think that knowing non-default values could be more useful, or atleast
> there should be a way to know the non-default options for the database.
>
The defaults option is false by default; if a user does not specify it,
the reconstructed DDL only includes non-default parameters. In my opinion,
we should retain this flag for users who wish to see all available
parameters for easier manual editing later. While simple database objects
have fewer parameters, this flag is essential for complex TABLE or FUNCTION
syntax where the parameter list is extensive.
> Also, the option strategy is missing in the output, is it deliberate? If
> yes, why?
>
> On Thu, 5 Mar 2026 at 01:50, Akshay Joshi <[email protected]>
> wrote:
>
>> I’ve addressed all of your comments.
>>
>> Attached is the *v11 patch*, now ready for further review.
>>
>> On Wed, Mar 4, 2026 at 7:31 PM Japin Li <[email protected]> wrote:
>>
>>> On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <
>>> [email protected]> wrote:
>>> > Thanks for the review, Japin. I’ve addressed all of your comments. I
>>> also added a check to throw an error if an option
>>> > appears more than once.
>>> >
>>> > Attached is the v10 patch, now ready for further review.
>>> >
>>>
>>> Thanks for updating the patch. Here are some comments on v10.
>>>
>>> 1.
>>> + * db_oid - OID/Name of the database for which to generate the DDL.
>>>
>>> Should the comment be updated? The code only accepts an OID for `db_oid`,
>>> database names are not supported.
>>>
>>> 2.
>>> + /* 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",
>>> +
>>> quote_identifier(dbowner));
>>> + }
>>>
>>> `dbowner` is only needed inside this `if` — how about declaring it there
>>> to
>>> reduce its scope?
>>>
>>> 3.
>>> + /* If is_with_defaults is true, then 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)));
>>> + }
>>>
>>> How about cache the result of `pg_encoding_to_char()` in a local
>>> variable to
>>> avoid calling it twice?
>>>
>>> --
>>> Regards,
>>> Japin Li
>>> ChengDu WenWu Information Technology Co., Ltd.
>>>
>>
>
> --
> Regards,
> Rafia Sabih
> CYBERTEC PostgreSQL International GmbH
>
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-06 07:37 Rafia Sabih <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Rafia Sabih @ 2026-03-06 07:37 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Japin Li <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Thu, 5 Mar 2026 at 08:16, Akshay Joshi <[email protected]>
wrote:
>
>
> On Thu, Mar 5, 2026 at 8:15 PM Rafia Sabih <[email protected]>
> wrote:
>
>> I like the idea for this patch and find this useful.
>>
>> I have few comments for the patch, looking at the test cases added in the
>> patch, it is not clear to me what is the default value for each of the
>> options. For example, in
>>
>
> Following the postgresql documentation, I created ddl_defaults.h to
> handle default options. This will be used across all database objects in
> future pg_get_<object>_ddl patches.
>
>
>> +-- With No Tablespace
>> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults',
>> true, 'tablespace', false));
>> +
>> ddl_filter
>>
>>
>> +----------------------------------------------------------------------------------------------------------------------------------------------------------
>> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
>> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
>> IS_TEMPLATE = false;
>> +(1 row)
>> +
>> owner option is omitted but the output contains the owner information. So
>> does it mean that the default value for each option is true?
>> But that doesn't seem so, because omitting pretty option, doesn't give
>> output in pretty, rather pretty is used only when provided.
>> I find this rather confusing, it is worth documenting what is the default
>> value for each of these options.
>>
>
> In my view, every database has an owner; it's a required attribute,
> not an optional one with a "default" value. The OWNER clause is correctly
> controlled only by the PG_DDL_NO_OWNER flag. Users must explicitly provide (*'owner',
> false/'no'/'off')* to opt out of including ownership info in the
> DDL. When comparing the Owner and Pretty options: by default, the Owner is
> included in the DDL reconstruction, whereas the Pretty option is set to
> false by default.
>
> Right, I think this needs to be added in the documentation as part of this
patch. Basically, here
<para>
+ <literal>'pretty', true</literal> (or <literal>'pretty',
'yes'</literal>):
+ Adds newlines and indentation for better readability.
+ </para>
it needs to have a line more saying, by default this option is off.
Similarly for other options, like for the owner it is by default true, etc.
> In my initial patches, I used flags like --no-owner and
> --no-tablespace because they are very intuitive for users. However,
> reviewers noted that this style is unique to pg_dump and not used
> elsewhere. Consequently, I switched to the existing name-value pair format
> using VARIADIC arguments.
>
>>
>> Similarly, what is the expected behaviour when defaults option is not
>> provided,
>> +-- With No Owner
>> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner',
>> false));
>> + 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',
>> true, 'tablespace', false));
>> +
>> ddl_filter
>>
>>
>> +----------------------------------------------------------------------------------------------------------------------------------------------------------
>> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
>> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
>> IS_TEMPLATE = false;
>> +(1 row)
>> Why is connection_limit present in both of these cases...?
>>
>> Another thing, what is the significance of having defaults option,
>> because I think that knowing non-default values could be more useful, or
>> atleast there should be a way to know the non-default options for the
>> database.
>>
>
> The defaults option is false by default; if a user does not specify
> it, the reconstructed DDL only includes non-default parameters. In my
> opinion, we should retain this flag for users who wish to see all available
> parameters for easier manual editing later. While simple database objects
> have fewer parameters, this flag is essential for complex TABLE or FUNCTION
> syntax where the parameter list is extensive.
>
This information should also be included in the documentation.
>
>
>> Also, the option strategy is missing in the output, is it deliberate? If
>> yes, why?
>>
>
>> On Thu, 5 Mar 2026 at 01:50, Akshay Joshi <[email protected]>
>> wrote:
>>
>>> I’ve addressed all of your comments.
>>>
>>> Attached is the *v11 patch*, now ready for further review.
>>>
>>> On Wed, Mar 4, 2026 at 7:31 PM Japin Li <[email protected]> wrote:
>>>
>>>> On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <
>>>> [email protected]> wrote:
>>>> > Thanks for the review, Japin. I’ve addressed all of your comments. I
>>>> also added a check to throw an error if an option
>>>> > appears more than once.
>>>> >
>>>> > Attached is the v10 patch, now ready for further review.
>>>> >
>>>>
>>>> Thanks for updating the patch. Here are some comments on v10.
>>>>
>>>> 1.
>>>> + * db_oid - OID/Name of the database for which to generate the DDL.
>>>>
>>>> Should the comment be updated? The code only accepts an OID for
>>>> `db_oid`,
>>>> database names are not supported.
>>>>
>>>> 2.
>>>> + /* 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",
>>>> +
>>>> quote_identifier(dbowner));
>>>> + }
>>>>
>>>> `dbowner` is only needed inside this `if` — how about declaring it
>>>> there to
>>>> reduce its scope?
>>>>
>>>> 3.
>>>> + /* If is_with_defaults is true, then 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)));
>>>> + }
>>>>
>>>> How about cache the result of `pg_encoding_to_char()` in a local
>>>> variable to
>>>> avoid calling it twice?
>>>>
>>>> --
>>>> Regards,
>>>> Japin Li
>>>> ChengDu WenWu Information Technology Co., Ltd.
>>>>
>>>
>>
>> --
>> Regards,
>> Rafia Sabih
>> CYBERTEC PostgreSQL International GmbH
>>
>
--
Regards,
Rafia Sabih
CYBERTEC PostgreSQL International GmbH
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-06 11:51 Akshay Joshi <[email protected]>
parent: Rafia Sabih <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-06 11:51 UTC (permalink / raw)
To: Rafia Sabih <[email protected]>; +Cc: Japin Li <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
I have updated the documentation.
Attached is the *v12 patch*, now ready for further review.
On Fri, Mar 6, 2026 at 1:07 PM Rafia Sabih <[email protected]>
wrote:
>
>
> On Thu, 5 Mar 2026 at 08:16, Akshay Joshi <[email protected]>
> wrote:
>
>>
>>
>> On Thu, Mar 5, 2026 at 8:15 PM Rafia Sabih <[email protected]>
>> wrote:
>>
>>> I like the idea for this patch and find this useful.
>>>
>>> I have few comments for the patch, looking at the test cases added in
>>> the patch, it is not clear to me what is the default value for each of the
>>> options. For example, in
>>>
>>
>> Following the postgresql documentation, I created ddl_defaults.h to
>> handle default options. This will be used across all database objects in
>> future pg_get_<object>_ddl patches.
>>
>>
>>> +-- With No Tablespace
>>> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults',
>>> true, 'tablespace', false));
>>> +
>>> ddl_filter
>>>
>>>
>>> +----------------------------------------------------------------------------------------------------------------------------------------------------------
>>> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
>>> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
>>> IS_TEMPLATE = false;
>>> +(1 row)
>>> +
>>> owner option is omitted but the output contains the owner information.
>>> So does it mean that the default value for each option is true?
>>> But that doesn't seem so, because omitting pretty option, doesn't give
>>> output in pretty, rather pretty is used only when provided.
>>> I find this rather confusing, it is worth documenting what is the
>>> default value for each of these options.
>>>
>>
>> In my view, every database has an owner; it's a required attribute,
>> not an optional one with a "default" value. The OWNER clause is correctly
>> controlled only by the PG_DDL_NO_OWNER flag. Users must explicitly provide (*'owner',
>> false/'no'/'off')* to opt out of including ownership info in the
>> DDL. When comparing the Owner and Pretty options: by default, the Owner is
>> included in the DDL reconstruction, whereas the Pretty option is set to
>> false by default.
>>
>> Right, I think this needs to be added in the documentation as part of
> this patch. Basically, here
> <para>
> + <literal>'pretty', true</literal> (or <literal>'pretty',
> 'yes'</literal>):
> + Adds newlines and indentation for better readability.
> + </para>
> it needs to have a line more saying, by default this option is off.
> Similarly for other options, like for the owner it is by default true, etc.
>
>> In my initial patches, I used flags like --no-owner and
>> --no-tablespace because they are very intuitive for users. However,
>> reviewers noted that this style is unique to pg_dump and not used
>> elsewhere. Consequently, I switched to the existing name-value pair format
>> using VARIADIC arguments.
>>
>>>
>>> Similarly, what is the expected behaviour when defaults option is not
>>> provided,
>>> +-- With No Owner
>>> +SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner',
>>> false));
>>> + 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',
>>> true, 'tablespace', false));
>>> +
>>> ddl_filter
>>>
>>>
>>> +----------------------------------------------------------------------------------------------------------------------------------------------------------
>>> + CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after
>>> ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123
>>> IS_TEMPLATE = false;
>>> +(1 row)
>>> Why is connection_limit present in both of these cases...?
>>>
>>> Another thing, what is the significance of having defaults option,
>>> because I think that knowing non-default values could be more useful, or
>>> atleast there should be a way to know the non-default options for the
>>> database.
>>>
>>
>> The defaults option is false by default; if a user does not specify
>> it, the reconstructed DDL only includes non-default parameters. In my
>> opinion, we should retain this flag for users who wish to see all available
>> parameters for easier manual editing later. While simple database objects
>> have fewer parameters, this flag is essential for complex TABLE or FUNCTION
>> syntax where the parameter list is extensive.
>>
> This information should also be included in the documentation.
>
>>
>>
>>> Also, the option strategy is missing in the output, is it deliberate? If
>>> yes, why?
>>>
>>
>>> On Thu, 5 Mar 2026 at 01:50, Akshay Joshi <[email protected]>
>>> wrote:
>>>
>>>> I’ve addressed all of your comments.
>>>>
>>>> Attached is the *v11 patch*, now ready for further review.
>>>>
>>>> On Wed, Mar 4, 2026 at 7:31 PM Japin Li <[email protected]> wrote:
>>>>
>>>>> On Wed, 04 Mar 2026 at 18:29, Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>> > Thanks for the review, Japin. I’ve addressed all of your comments. I
>>>>> also added a check to throw an error if an option
>>>>> > appears more than once.
>>>>> >
>>>>> > Attached is the v10 patch, now ready for further review.
>>>>> >
>>>>>
>>>>> Thanks for updating the patch. Here are some comments on v10.
>>>>>
>>>>> 1.
>>>>> + * db_oid - OID/Name of the database for which to generate the DDL.
>>>>>
>>>>> Should the comment be updated? The code only accepts an OID for
>>>>> `db_oid`,
>>>>> database names are not supported.
>>>>>
>>>>> 2.
>>>>> + /* 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",
>>>>> +
>>>>> quote_identifier(dbowner));
>>>>> + }
>>>>>
>>>>> `dbowner` is only needed inside this `if` — how about declaring it
>>>>> there to
>>>>> reduce its scope?
>>>>>
>>>>> 3.
>>>>> + /* If is_with_defaults is true, then 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)));
>>>>> + }
>>>>>
>>>>> How about cache the result of `pg_encoding_to_char()` in a local
>>>>> variable to
>>>>> avoid calling it twice?
>>>>>
>>>>> --
>>>>> Regards,
>>>>> Japin Li
>>>>> ChengDu WenWu Information Technology Co., Ltd.
>>>>>
>>>>
>>>
>>> --
>>> Regards,
>>> Rafia Sabih
>>> CYBERTEC PostgreSQL International GmbH
>>>
>>
>
> --
> Regards,
> Rafia Sabih
> CYBERTEC PostgreSQL International GmbH
>
Attachments:
[application/octet-stream] v12-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (33.9K, ../../CANxoLDeCXmzMnmhYSqL+kBEVynGQ99TyJp74UWwj_jeGS0J_6g@mail.gmail.com/3-v12-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From 89cfa436e7cd815f56fdf7a44981d9b60200dd0c Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Fri, 6 Mar 2026 16:46:02 +0530
Subject: [PATCH v12] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1' or 'no'/'off'/false/'0'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 91 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 403 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 37 +++
src/test/regress/expected/database.out | 180 ++++++++++
src/test/regress/sql/database.sql | 112 +++++++
7 files changed, 836 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 294f45e82a3..6915408ae30 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,95 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> 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., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Formats the output with newlines and indentation for better readability.
+ This option defaults to false.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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.
+ This option defaults to false.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..fa48e2f0775 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -372,3 +372,9 @@ CREATE OR REPLACE FUNCTION ts_debug(document text,
BEGIN ATOMIC
SELECT * FROM ts_debug(get_current_ts_config(), $1);
END;
+
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+RETURNS text
+LANGUAGE internal
+AS 'pg_get_database_ddl';
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f16f1535785..bbf82c1d6c0 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,45 @@
#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
+
+/*
+ * 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint32 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,364 @@ 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 */
+ appendStringInfoSpaces(buf, nSpaces);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ for (;;)
+ {
+ int needed;
+
+ va_start(args, fmt);
+ needed = appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(buf, needed);
+ }
+}
+
+/**
+ * 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;
+ bool found = false;
+
+ /* 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;
+
+ 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("argument %d: invalid value \"%s\" for key \"%s\"",
+ i + 2, valstr, name),
+ errhint("Valid values are: true, false, yes, no, 1, 0, on, off.")));
+ pfree(valstr);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("argument %d: value for key \"%s\" must be boolean or text type",
+ i + 2, name)));
+ }
+
+ /*
+ * 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 name or 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(string_to_text(res));
+}
+
+static char *
+pg_get_database_ddl_worker(Oid db_oid, uint32 ddl_flags)
+{
+ const char *encoding = 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;
+ 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) ? 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);
+
+ /* 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 owner is not omitted */
+ if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
+ {
+ char *dbowner = GetUserNameFromId(dbform->datdba, false);
+
+ if (dbowner != NULL)
+ {
+ get_formatted_string(&buf, pretty_flags, 8, "OWNER = %s",
+ quote_identifier(dbowner));
+ }
+ }
+
+ /* Set the ENCODING in the DDL */
+ encoding = pg_encoding_to_char(dbform->encoding);
+
+ if (is_with_defaults ||
+ pg_strcasecmp(encoding, DDL_DEFAULTS.DATABASE.ENCODING) != 0)
+ {
+ get_formatted_string(&buf, pretty_flags, 8, "ENCODING = %s",
+ quote_literal_cstr(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 tablespace is not omitted */
+ 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 (is_with_defaults || 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 361e2cfffeb..1e2f7d3ac35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4034,6 +4034,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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..9f21c42c05a
--- /dev/null
+++ b/src/include/utils/ddl_defaults.h
@@ -0,0 +1,37 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+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..5d2834771ac 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,132 @@ 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);
+ pg_get_database_ddl
+---------------------
+
+(1 row)
+
+-- Without options
+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', false));
+ 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', true, 'tablespace', false));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ OWNER = regress_datdba_after
+ ENCODING = 'UTF8'
+ TABLESPACE = pg_default
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ENCODING = 'UTF8'
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using '1' and '0'
+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 IS_TEMPLATE = false;
+(1 row)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ENCODING = 'UTF8' ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: argument 2: invalid value "invalid" for key "owner"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
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..b67b56006d8 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,62 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-06 14:43 Japin Li <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Japin Li @ 2026-03-06 14:43 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
Hi, Akshay
On Fri, 06 Mar 2026 at 17:21, Akshay Joshi <[email protected]> wrote:
> I have updated the documentation.
> Attached is the v12 patch, now ready for further review.
>
Thanks for updating the patch.
I might not have expressed myself clearly in my last email — apologies.
Here's a diff that should make it clearer.
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index bbf82c1d6c0..1ed56ee71ab 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13859,7 +13859,6 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
bool *nulls;
Oid *types;
int nargs;
- bool found = false;
/* Extract variadic arguments */
nargs = extract_variadic_args(fcinfo, variadic_start, true,
@@ -13887,8 +13886,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
{
char *name;
bool bval;
-
- found = false;
+ bool found = false;
/* Key must not be null */
if (nulls[i])
@@ -13925,8 +13923,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
if (!parse_bool(valstr, &bval))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("argument %d: invalid value \"%s\" for key \"%s\"",
- i + 2, valstr, name),
+ 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);
}
@@ -13934,8 +13932,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("argument %d: value for key \"%s\" must be boolean or text type",
- i + 2, name)));
+ 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]))));
}
/*
@@ -13983,7 +13981,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
/*
* pg_get_database_ddl
*
- * Generate a CREATE DATABASE statement for the specified database name or oid.
+ * 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.
--
Regards,
Japin Li
ChengDu WenWu Information Technology Co., Ltd.
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-09 12:08 Akshay Joshi <[email protected]>
parent: Japin Li <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-09 12:08 UTC (permalink / raw)
To: Japin Li <[email protected]>; +Cc: Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
I have resolved the review comments. Removed the hardcoded 'utf-8'
encoding, the logic now retrieves the encoding dynamically. This ensures
accuracy because the default encoding for CREATE DATABASE is derived from
the template database (template1) rather than being a static value.
Attached is the *v13 patch*, now ready for further review.
On Fri, Mar 6, 2026 at 8:13 PM Japin Li <[email protected]> wrote:
>
> Hi, Akshay
>
> On Fri, 06 Mar 2026 at 17:21, Akshay Joshi <[email protected]>
> wrote:
> > I have updated the documentation.
> > Attached is the v12 patch, now ready for further review.
> >
>
> Thanks for updating the patch.
>
> I might not have expressed myself clearly in my last email — apologies.
> Here's a diff that should make it clearer.
>
> diff --git a/src/backend/utils/adt/ruleutils.c
> b/src/backend/utils/adt/ruleutils.c
> index bbf82c1d6c0..1ed56ee71ab 100644
> --- a/src/backend/utils/adt/ruleutils.c
> +++ b/src/backend/utils/adt/ruleutils.c
> @@ -13859,7 +13859,6 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> bool *nulls;
> Oid *types;
> int nargs;
> - bool found = false;
>
> /* Extract variadic arguments */
> nargs = extract_variadic_args(fcinfo, variadic_start, true,
> @@ -13887,8 +13886,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> {
> char *name;
> bool bval;
> -
> - found = false;
> + bool found = false;
>
> /* Key must not be null */
> if (nulls[i])
> @@ -13925,8 +13923,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> if (!parse_bool(valstr, &bval))
> ereport(ERROR,
>
> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> - errmsg("argument %d:
> invalid value \"%s\" for key \"%s\"",
> - i + 2,
> valstr, name),
> + 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);
> }
> @@ -13934,8 +13932,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> {
> ereport(ERROR,
>
> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> - errmsg("argument %d: value for
> key \"%s\" must be boolean or text type",
> - i + 2, name)));
> + 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]))));
> }
>
> /*
> @@ -13983,7 +13981,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> /*
> * pg_get_database_ddl
> *
> - * Generate a CREATE DATABASE statement for the specified database name
> or oid.
> + * 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.
>
> --
> Regards,
> Japin Li
> ChengDu WenWu Information Technology Co., Ltd.
>
Attachments:
[application/octet-stream] v13-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (34.3K, ../../CANxoLDf+Ak=S+V1Rx25Eqh7r6cuWZtHYiEo_AZsn9eqiWbVGEg@mail.gmail.com/3-v13-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From f0e001b44e85944cf479987f5545fa88a0b56677 Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Fri, 6 Mar 2026 16:46:02 +0530
Subject: [PATCH v13] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1' or 'no'/'off'/false/'0'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 91 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 413 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 35 ++
src/test/regress/expected/database.out | 186 ++++++++++
src/test/regress/sql/database.sql | 120 +++++++
7 files changed, 858 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 294f45e82a3..6915408ae30 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,95 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> 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., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Formats the output with newlines and indentation for better readability.
+ This option defaults to false.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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.
+ This option defaults to false.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..fa48e2f0775 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -372,3 +372,9 @@ CREATE OR REPLACE FUNCTION ts_debug(document text,
BEGIN ATOMIC
SELECT * FROM ts_debug(get_current_ts_config(), $1);
END;
+
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+RETURNS text
+LANGUAGE internal
+AS 'pg_get_database_ddl';
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f16f1535785..e762a628f8c 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,45 @@
#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
+
+/*
+ * 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint32 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,374 @@ 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 */
+ appendStringInfoSpaces(buf, nSpaces);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ for (;;)
+ {
+ int needed;
+
+ va_start(args, fmt);
+ needed = appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(buf, needed);
+ }
+}
+
+/*
+ * 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(string_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;
+ AclResult aclresult;
+ HeapTuple tmpl_tuple;
+ int tmpl_encoding = -1;
+
+ /* 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);
+
+ /* 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 owner is not omitted */
+ if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
+ {
+ char *dbowner = GetUserNameFromId(dbform->datdba, false);
+
+ get_formatted_string(&buf, 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(&buf, pretty_flags, 8, "ENCODING = %s",
+ quote_literal_cstr(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 tablespace is not omitted */
+ 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 (is_with_defaults || 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 361e2cfffeb..1e2f7d3ac35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4034,6 +4034,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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..eb9c7750651
--- /dev/null
+++ b/src/include/utils/ddl_defaults.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+static const struct
+{
+ struct
+ {
+ const char *TABLESPACE;
+ int CONN_LIMIT;
+ bool ALLOW_CONN;
+ } DATABASE;
+
+ /* Add more object types as needed */
+} DDL_DEFAULTS = {
+
+ .DATABASE = {
+ .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..76eca22cf3a 100644
--- a/src/test/regress/expected/database.out
+++ b/src/test/regress/expected/database.out
@@ -1,3 +1,65 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +78,130 @@ 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);
+ pg_get_database_ddl
+---------------------
+
+(1 row)
+
+-- Without options
+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', false));
+ 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', true, 'tablespace', false));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ OWNER = regress_datdba_after
+ TABLESPACE = pg_default
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: value for option "owner" at position 2 has invalid value "invalid"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
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..9ef926fac9c 100644
--- a/src/test/regress/sql/database.sql
+++ b/src/test/regress/sql/database.sql
@@ -1,3 +1,67 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +83,62 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-10 10:16 Kirill Reshke <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Kirill Reshke @ 2026-03-10 10:16 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Japin Li <[email protected]>; Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Mon, 9 Mar 2026 at 17:08, Akshay Joshi <[email protected]> wrote:
>
> I have resolved the review comments. Removed the hardcoded 'utf-8' encoding, the logic now retrieves the encoding dynamically. This ensures accuracy because the default encoding for CREATE DATABASE is derived from the template database (template1) rather than being a static value.
>
> Attached is the v13 patch, now ready for further review.
>
> On Fri, Mar 6, 2026 at 8:13 PM Japin Li <[email protected]> wrote:
>>
>>
>> Hi, Akshay
>>
>> On Fri, 06 Mar 2026 at 17:21, Akshay Joshi <[email protected]> wrote:
>> > I have updated the documentation.
>> > Attached is the v12 patch, now ready for further review.
>> >
>>
>> Thanks for updating the patch.
>>
>> I might not have expressed myself clearly in my last email — apologies.
>> Here's a diff that should make it clearer.
>>
>> diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
>> index bbf82c1d6c0..1ed56ee71ab 100644
>> --- a/src/backend/utils/adt/ruleutils.c
>> +++ b/src/backend/utils/adt/ruleutils.c
>> @@ -13859,7 +13859,6 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
>> bool *nulls;
>> Oid *types;
>> int nargs;
>> - bool found = false;
>>
>> /* Extract variadic arguments */
>> nargs = extract_variadic_args(fcinfo, variadic_start, true,
>> @@ -13887,8 +13886,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
>> {
>> char *name;
>> bool bval;
>> -
>> - found = false;
>> + bool found = false;
>>
>> /* Key must not be null */
>> if (nulls[i])
>> @@ -13925,8 +13923,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
>> if (!parse_bool(valstr, &bval))
>> ereport(ERROR,
>> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
>> - errmsg("argument %d: invalid value \"%s\" for key \"%s\"",
>> - i + 2, valstr, name),
>> + 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);
>> }
>> @@ -13934,8 +13932,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
>> {
>> ereport(ERROR,
>> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
>> - errmsg("argument %d: value for key \"%s\" must be boolean or text type",
>> - i + 2, name)));
>> + 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]))));
>> }
>>
>> /*
>> @@ -13983,7 +13981,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start)
>> /*
>> * pg_get_database_ddl
>> *
>> - * Generate a CREATE DATABASE statement for the specified database name or oid.
>> + * 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.
>>
>> --
>> Regards,
>> Japin Li
>> ChengDu WenWu Information Technology Co., Ltd.
Hi!
I noticed this in v13: DDLOptionDef is missing from typedefs.list.
This results in pgident to incorrectly format sources.
--
Best regards,
Kirill Reshke
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-10 13:44 Akshay Joshi <[email protected]>
parent: Kirill Reshke <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Akshay Joshi @ 2026-03-10 13:44 UTC (permalink / raw)
To: Kirill Reshke <[email protected]>; +Cc: Japin Li <[email protected]>; Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
Thanks for the review. I wasn't aware of this change, as I'm still new to
PostgreSQL development.
Attached is the *v14 patch*, now ready for further review.
On Tue, Mar 10, 2026 at 3:46 PM Kirill Reshke <[email protected]>
wrote:
> On Mon, 9 Mar 2026 at 17:08, Akshay Joshi <[email protected]>
> wrote:
> >
> > I have resolved the review comments. Removed the hardcoded 'utf-8'
> encoding, the logic now retrieves the encoding dynamically. This ensures
> accuracy because the default encoding for CREATE DATABASE is derived from
> the template database (template1) rather than being a static value.
> >
> > Attached is the v13 patch, now ready for further review.
> >
> > On Fri, Mar 6, 2026 at 8:13 PM Japin Li <[email protected]> wrote:
> >>
> >>
> >> Hi, Akshay
> >>
> >> On Fri, 06 Mar 2026 at 17:21, Akshay Joshi <
> [email protected]> wrote:
> >> > I have updated the documentation.
> >> > Attached is the v12 patch, now ready for further review.
> >> >
> >>
> >> Thanks for updating the patch.
> >>
> >> I might not have expressed myself clearly in my last email — apologies.
> >> Here's a diff that should make it clearer.
> >>
> >> diff --git a/src/backend/utils/adt/ruleutils.c
> b/src/backend/utils/adt/ruleutils.c
> >> index bbf82c1d6c0..1ed56ee71ab 100644
> >> --- a/src/backend/utils/adt/ruleutils.c
> >> +++ b/src/backend/utils/adt/ruleutils.c
> >> @@ -13859,7 +13859,6 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> >> bool *nulls;
> >> Oid *types;
> >> int nargs;
> >> - bool found = false;
> >>
> >> /* Extract variadic arguments */
> >> nargs = extract_variadic_args(fcinfo, variadic_start, true,
> >> @@ -13887,8 +13886,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> >> {
> >> char *name;
> >> bool bval;
> >> -
> >> - found = false;
> >> + bool found = false;
> >>
> >> /* Key must not be null */
> >> if (nulls[i])
> >> @@ -13925,8 +13923,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> >> if (!parse_bool(valstr, &bval))
> >> ereport(ERROR,
> >>
> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> >> - errmsg("argument %d:
> invalid value \"%s\" for key \"%s\"",
> >> - i + 2,
> valstr, name),
> >> + 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);
> >> }
> >> @@ -13934,8 +13932,8 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> >> {
> >> ereport(ERROR,
> >>
> (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
> >> - errmsg("argument %d: value for
> key \"%s\" must be boolean or text type",
> >> - i + 2, name)));
> >> + 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]))));
> >> }
> >>
> >> /*
> >> @@ -13983,7 +13981,7 @@ parse_ddl_options(FunctionCallInfo fcinfo, int
> variadic_start)
> >> /*
> >> * pg_get_database_ddl
> >> *
> >> - * Generate a CREATE DATABASE statement for the specified database
> name or oid.
> >> + * 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.
> >>
> >> --
> >> Regards,
> >> Japin Li
> >> ChengDu WenWu Information Technology Co., Ltd.
>
>
> Hi!
> I noticed this in v13: DDLOptionDef is missing from typedefs.list.
> This results in pgident to incorrectly format sources.
>
>
>
> --
> Best regards,
> Kirill Reshke
>
>
>
Attachments:
[application/octet-stream] v14-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (34.7K, ../../CANxoLDeP1_mxdEUVwW3o=tjoWwpOVUs0mMh-mdV23hT5FTSqpg@mail.gmail.com/3-v14-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From a8e1ac58c7c6dff0fae68d769ef722ef2203d490 Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Fri, 6 Mar 2026 16:46:02 +0530
Subject: [PATCH v14] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1' or 'no'/'off'/false/'0'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 91 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/ruleutils.c | 413 +++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +
src/include/utils/ddl_defaults.h | 35 ++
src/test/regress/expected/database.out | 186 ++++++++++
src/test/regress/sql/database.sql | 120 +++++++
src/tools/pgindent/typedefs.list | 2 +
8 files changed, 860 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 294f45e82a3..6915408ae30 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,95 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> 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., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Formats the output with newlines and indentation for better readability.
+ This option defaults to false.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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.
+ This option defaults to false.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..fa48e2f0775 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -372,3 +372,9 @@ CREATE OR REPLACE FUNCTION ts_debug(document text,
BEGIN ATOMIC
SELECT * FROM ts_debug(get_current_ts_config(), $1);
END;
+
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+RETURNS text
+LANGUAGE internal
+AS 'pg_get_database_ddl';
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f16f1535785..998b31a5733 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,45 @@
#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
+
+/*
+ * 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},
+};
+
+
/* 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
@@ -547,6 +584,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, uint32 ddl_flags);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
@@ -13760,3 +13802,374 @@ 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 */
+ appendStringInfoSpaces(buf, nSpaces);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ for (;;)
+ {
+ int needed;
+
+ va_start(args, fmt);
+ needed = appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(buf, needed);
+ }
+}
+
+/*
+ * 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(string_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;
+ AclResult aclresult;
+ HeapTuple tmpl_tuple;
+ int tmpl_encoding = -1;
+
+ /* 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);
+
+ /* 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 owner is not omitted */
+ if (OidIsValid(dbform->datdba) && !(ddl_flags & PG_DDL_NO_OWNER))
+ {
+ char *dbowner = GetUserNameFromId(dbform->datdba, false);
+
+ get_formatted_string(&buf, 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(&buf, pretty_flags, 8, "ENCODING = %s",
+ quote_literal_cstr(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 tablespace is not omitted */
+ 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 (is_with_defaults || 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 361e2cfffeb..1e2f7d3ac35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4034,6 +4034,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ 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..8cc3733c11f
--- /dev/null
+++ b/src/include/utils/ddl_defaults.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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
+
+static const struct
+{
+ struct
+ {
+ const char *TABLESPACE;
+ int CONN_LIMIT;
+ bool ALLOW_CONN;
+ } DATABASE;
+
+ /* Add more object types as needed */
+} DDL_DEFAULTS = {
+
+ .DATABASE = {
+ .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..76eca22cf3a 100644
--- a/src/test/regress/expected/database.out
+++ b/src/test/regress/expected/database.out
@@ -1,3 +1,65 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +78,130 @@ 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);
+ pg_get_database_ddl
+---------------------
+
+(1 row)
+
+-- Without options
+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', false));
+ 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', true, 'tablespace', false));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ OWNER = regress_datdba_after
+ TABLESPACE = pg_default
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: value for option "owner" at position 2 has invalid value "invalid"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
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..9ef926fac9c 100644
--- a/src/test/regress/sql/database.sql
+++ b/src/test/regress/sql/database.sql
@@ -1,3 +1,67 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +83,62 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3250564d4ff..94980c3111a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -602,6 +602,8 @@ CycleCtr
DBState
DbOidName
DCHCacheEntry
+DDL_DEFAULTS
+DDLOptionDef
DEADLOCK_INFO
DECountItem
DH
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-10 22:05 Zsolt Parragi <[email protected]>
parent: Akshay Joshi <[email protected]>
0 siblings, 1 reply; 67+ messages in thread
From: Zsolt Parragi @ 2026-03-10 22:05 UTC (permalink / raw)
To: Akshay Joshi <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Japin Li <[email protected]>; Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
Hello
Is ruleutils.c the best place for this function?
It's already huge, and it has a different scope: "Functions to convert
stored expressions/querytrees back to source text"
+ /* 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)));
pg_dumpall only shows this for binary upgrade, otherwise skips it. Is
it okay for this command to print it by default, shouldn't it depend
on is_with_defaults or something similar?
+#ifndef DDL_DEFAULTS_H
+#define DDL_DEFAULTS_H
+
+static const struct
+{
+ struct
+ {
....
This file seems strange. A static const struct in a header with
uppercase names doesn't seem to follow postgres conventions?
DATCONNLIMIT_UNLIMITED alredy exists as a definition, and probably
should be used instead or referenced, or the existing uses should
refer to the new way of defining it.
+ /* 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);
Can these be ever nulls?
Also, pg_dump only emits LOCALE if ctype==collate, shouldn't this
follow the same pattern?
+ else if (is_with_defaults)
+ get_formatted_string(&buf, pretty_flags, 8, "LOCALE_PROVIDER = libc");
Doesn't pg_dump always emit this? Shouldn't this function follow the
same convention? Emitting it seems to be a safer default, in case
postgres ever changes this.
+ /* Build the CREATE DATABASE statement */
+ appendStringInfo(&buf, "CREATE DATABASE %s",
+ quote_identifier(dbform->datname.data));
+ get_formatted_string(&buf, pretty_flags, 4, "WITH");
Shouldn't we only emit "WITH" if it is actually followed by something,
not unconditionally?
+/*
+ * get_formatted_string
+ *
+ * Return a formatted version of the string.
But it's a void function.
+ else if (!attr_isnull)
+ get_formatted_string(&buf, pretty_flags, 8, "LOCALE = %s",
+ quote_literal_cstr(TextDatumGetCString(dbvalue)));
+
Can this ever happen, shouldn't it be an assertion instead?
^ permalink raw reply [nested|flat] 67+ messages in thread
* Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement
@ 2026-03-11 18:56 Akshay Joshi <[email protected]>
parent: Zsolt Parragi <[email protected]>
0 siblings, 0 replies; 67+ messages in thread
From: Akshay Joshi @ 2026-03-11 18:56 UTC (permalink / raw)
To: Zsolt Parragi <[email protected]>; +Cc: Kirill Reshke <[email protected]>; Japin Li <[email protected]>; Rafia Sabih <[email protected]>; Álvaro Herrera <[email protected]>; Euler Taveira <[email protected]>; Amul Sul <[email protected]>; Andrew Dunstan <[email protected]>; Chao Li <[email protected]>; Quan Zongliang <[email protected]>; pgsql-hackers
On Wed, Mar 11, 2026 at 3:35 AM Zsolt Parragi <[email protected]>
wrote:
> Hello
>
> Is ruleutils.c the best place for this function?
>
> It's already huge, and it has a different scope: "Functions to convert
> stored expressions/querytrees back to source text"
>
Created the ddlutils.c file.
>
> + /* 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)));
>
> pg_dumpall only shows this for binary upgrade, otherwise skips it. Is
> it okay for this command to print it by default, shouldn't it depend
> on is_with_defaults or something similar?
>
Shows only when `is_with_defaults` is true.
>
> +#ifndef DDL_DEFAULTS_H
> +#define DDL_DEFAULTS_H
> +
> +static const struct
> +{
> + struct
> + {
> ....
>
> This file seems strange. A static const struct in a header with
> uppercase names doesn't seem to follow postgres conventions?
> DATCONNLIMIT_UNLIMITED alredy exists as a definition, and probably
> should be used instead or referenced, or the existing uses should
> refer to the new way of defining it.
>
Removed the header file and implemented an alternative logic. Note that
a similar file may be necessary in the future to handle default values for
other pg_get_<object>_ddl.
>
> + /* 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);
>
> Can these be ever nulls?
>
> Also, pg_dump only emits LOCALE if ctype==collate, shouldn't this
> follow the same pattern?
>
> + else if (is_with_defaults)
> + get_formatted_string(&buf, pretty_flags, 8, "LOCALE_PROVIDER = libc");
>
> Doesn't pg_dump always emit this? Shouldn't this function follow the
> same convention? Emitting it seems to be a safer default, in case
> postgres ever changes this.
>
> + /* Build the CREATE DATABASE statement */
> + appendStringInfo(&buf, "CREATE DATABASE %s",
> + quote_identifier(dbform->datname.data));
> + get_formatted_string(&buf, pretty_flags, 4, "WITH");
>
> Shouldn't we only emit "WITH" if it is actually followed by something,
> not unconditionally?
>
> +/*
> + * get_formatted_string
> + *
> + * Return a formatted version of the string.
>
> But it's a void function.
>
>
> + else if (!attr_isnull)
> + get_formatted_string(&buf, pretty_flags, 8, "LOCALE = %s",
> + quote_literal_cstr(TextDatumGetCString(dbvalue)));
> +
>
> Can this ever happen, shouldn't it be an assertion instead?
>
Fixed all the preceding review comments.
Attached is the *v15 patch*, now ready for further review.
Attachments:
[application/x-patch] v15-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch (35.9K, ../../CANxoLDfLDa6Oh9y=QtVoCd=03b9ydeFF6fUe8vR1wPYU7refBg@mail.gmail.com/3-v15-0001-Add-pg_get_database_ddl-function-to-reconstruct-ddl.patch)
download | inline diff:
From 757098bb278cc6ee1de9b3d1022829f4b74ffbc3 Mon Sep 17 00:00:00 2001
From: Akshay Joshi <[email protected]>
Date: Fri, 6 Mar 2026 16:46:02 +0530
Subject: [PATCH v15] 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 OID.
Supported ddl_options are 'pretty', 'owner', 'tablespace' and 'defaults' and respective
values could be 'yes'/'on'/true/'1' or 'no'/'off'/false/'0'.
Usage:
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes');
SELECT pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1');
SELECT pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on');
SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no');
Reference: PG-150
Author: Akshay Joshi <[email protected]>
Reviewed-by: Álvaro Herrera <[email protected]>
Reviewed-by: Euler Taveira <[email protected]>
Reviewed-by: Quan Zongliang <[email protected]>
Reviewed-by: Japin Li <[email protected]>
Reviewed-by: Chao Li <[email protected]>
Reviewed-by: Zsolt Parragi <[email protected]>
Reviewed-by: Rafia Sabih <[email protected]>
---
doc/src/sgml/func/func-info.sgml | 91 +++++
src/backend/catalog/system_functions.sql | 6 +
src/backend/utils/adt/Makefile | 1 +
src/backend/utils/adt/ddlutils.c | 486 +++++++++++++++++++++++
src/backend/utils/adt/meson.build | 1 +
src/backend/utils/adt/ruleutils.c | 2 +-
src/include/catalog/pg_proc.dat | 7 +
src/test/regress/expected/database.out | 186 +++++++++
src/test/regress/sql/database.sql | 120 ++++++
src/tools/pgindent/typedefs.list | 1 +
10 files changed, 900 insertions(+), 1 deletion(-)
create mode 100644 src/backend/utils/adt/ddlutils.c
diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml
index 294f45e82a3..6915408ae30 100644
--- a/doc/src/sgml/func/func-info.sgml
+++ b/doc/src/sgml/func/func-info.sgml
@@ -3845,4 +3845,95 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
</sect2>
+ <sect2 id="functions-get-object-ddl">
+ <title>Get Object DDL Functions</title>
+
+ <para>
+ The functions described in <xref linkend="functions-get-object-ddl-table"/>
+ 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.
+ </para>
+
+ <table id="functions-get-object-ddl-table">
+ <title>Get Object DDL Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <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>options</parameter>
+ <type>"any"</type> </optional> )
+ <returnvalue>text</returnvalue>
+ </para>
+ <para>
+ Reconstructs the <command>CREATE DATABASE</command> 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., <literal>'pretty', true, 'owner', false</literal>).
+ Supported options are explained below.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <parameter>options</parameter> for <function>pg_get_database_ddl</function>
+ 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
+ (<literal>true</literal>, <literal>false</literal>, <literal>yes</literal>,
+ <literal>no</literal>, <literal>1</literal>, <literal>0</literal>,
+ <literal>on</literal>, <literal>off</literal>):
+ <itemizedlist>
+ <listitem>
+ <para>
+ <literal>'pretty', true</literal> (or <literal>'pretty', 'yes'</literal>):
+ Formats the output with newlines and indentation for better readability.
+ This option defaults to false.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'owner', false</literal> (or <literal>'owner', 'no'</literal>):
+ Omits the <literal>OWNER</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'tablespace', false</literal> (or <literal>'tablespace', '0'</literal>):
+ Omits the <literal>TABLESPACE</literal> clause from the reconstructed statement.
+ This option defaults to true.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <literal>'defaults', true</literal> (or <literal>'defaults', '1'</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.
+ This option defaults to false.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ </sect2>
+
</sect1>
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 1c5b6d6df05..fa48e2f0775 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -372,3 +372,9 @@ CREATE OR REPLACE FUNCTION ts_debug(document text,
BEGIN ATOMIC
SELECT * FROM ts_debug(get_current_ts_config(), $1);
END;
+
+CREATE OR REPLACE FUNCTION
+ pg_get_database_ddl(database_id regdatabase, VARIADIC options "any" DEFAULT NULL)
+RETURNS text
+LANGUAGE internal
+AS 'pg_get_database_ddl';
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index a8fd680589f..4fdd541f7bf 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -102,6 +102,7 @@ OBJS = \
regproc.o \
ri_triggers.o \
rowtypes.o \
+ ddlutils.o \
ruleutils.o \
selfuncs.o \
skipsupport.o \
diff --git a/src/backend/utils/adt/ddlutils.c b/src/backend/utils/adt/ddlutils.c
new file mode 100644
index 00000000000..6e88b8a1a92
--- /dev/null
+++ b/src/backend/utils/adt/ddlutils.c
@@ -0,0 +1,486 @@
+/*-------------------------------------------------------------------------
+ *
+ * ddlutils.c
+ * 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.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/ddlutils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <stdarg.h>
+
+#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"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#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);
+ }
+ else
+ appendStringInfoChar(buf, ' ');
+
+ for (;;)
+ {
+ int needed;
+
+ va_start(args, fmt);
+ needed = appendStringInfoVA(buf, fmt, args);
+ va_end(args);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(buf, needed);
+ }
+}
+
+/*
+ * 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;
+}
diff --git a/src/backend/utils/adt/meson.build b/src/backend/utils/adt/meson.build
index fb8294d7e4a..f9893b5dfb0 100644
--- a/src/backend/utils/adt/meson.build
+++ b/src/backend/utils/adt/meson.build
@@ -98,6 +98,7 @@ backend_sources += files(
'regproc.c',
'ri_triggers.c',
'rowtypes.c',
+ 'ddlutils.c',
'ruleutils.c',
'selfuncs.c',
'skipsupport.c',
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f16f1535785..997c671aef0 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -57,6 +57,7 @@
#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/fmgroids.h"
@@ -547,7 +548,6 @@ static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
deparse_context *context,
bool showimplicit,
bool needcomma);
-
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 361e2cfffeb..1e2f7d3ac35 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4034,6 +4034,13 @@
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', provariadic => 'any', proisstrict => 'f',
+ provolatile => 's', prorettype => 'text',
+ proargtypes => 'regdatabase any',
+ proargmodes => '{i,v}',
+ proallargtypes => '{regdatabase,any}',
+ prosrc => 'pg_get_database_ddl' },
{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
diff --git a/src/test/regress/expected/database.out b/src/test/regress/expected/database.out
index 6b879b0f62a..76eca22cf3a 100644
--- a/src/test/regress/expected/database.out
+++ b/src/test/regress/expected/database.out
@@ -1,3 +1,65 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +78,130 @@ 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);
+ pg_get_database_ddl
+---------------------
+
+(1 row)
+
+-- Without options
+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', false));
+ 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', true, 'tablespace', false));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+ ddl_filter
+----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH OWNER = regress_datdba_after TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+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', true, 'owner', false, 'tablespace', false));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ CONNECTION LIMIT = 123;
+(1 row)
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ OWNER = regress_datdba_after
+ TABLESPACE = pg_default
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+ddl_filter
+CREATE DATABASE regression_utf8
+ WITH
+ ALLOW_CONNECTIONS = true
+ CONNECTION LIMIT = 123
+ IS_TEMPLATE = false;
+(1 row)
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH TABLESPACE = pg_default ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+ ddl_filter
+---------------------------------
+ CREATE DATABASE regression_utf8+
+ WITH +
+ CONNECTION LIMIT = 123;
+(1 row)
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+ ddl_filter
+-----------------------------------------------------------------------------------------------------------
+ CREATE DATABASE regression_utf8 WITH ALLOW_CONNECTIONS = true CONNECTION LIMIT = 123 IS_TEMPLATE = false;
+(1 row)
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+ERROR: option "owner" is specified more than once
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+ERROR: value for option "owner" at position 2 has invalid value "invalid"
+HINT: Valid values are: true, false, yes, no, 1, 0, on, off.
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
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..9ef926fac9c 100644
--- a/src/test/regress/sql/database.sql
+++ b/src/test/regress/sql/database.sql
@@ -1,3 +1,67 @@
+--
+-- 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'
+ );
+
+ -- Remove ENCODING assignments
+ cleaned_ddl := regexp_replace(
+ cleaned_ddl,
+ '\s*ENCODING\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 +83,62 @@ 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 options
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
+
+-- With No Owner
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false));
+
+-- With No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- With Pretty formatted
+\pset format unaligned
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true));
+
+-- With No Owner and No Tablespace
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false));
+
+-- With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'defaults', true));
+
+-- With No Owner, No Tablespace and With Defaults
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'pretty', true, 'owner', false, 'tablespace', false, 'defaults', true));
+
+-- Test with text values: 'yes', 'no', '1', '0', 'on', 'off'
+\pset format aligned
+-- Using 'yes' and 'no'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'no', 'defaults', 'yes'));
+
+-- Using '1' and '0'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', '0', 'tablespace', '0', 'defaults', '1'));
+
+-- Using 'on' and 'off'
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', 'off', 'pretty', 'on'));
+
+-- Mixed boolean and text values
+SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 'owner', false, 'defaults', 'true', 'tablespace', 'no'));
+
+-- Test duplicate option (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', false, 'owner', true);
+
+-- Test invalid text value (should error)
+SELECT pg_get_database_ddl('regression_utf8', 'owner', 'invalid');
+
DROP DATABASE regression_utf8;
+DROP FUNCTION ddl_filter(text);
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3da19d41413..622d10541d0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -602,6 +602,7 @@ CycleCtr
DBState
DbOidName
DCHCacheEntry
+DDLOptionDef
DEADLOCK_INFO
DECountItem
DH
--
2.51.0
^ permalink raw reply [nested|flat] 67+ messages in thread
end of thread, other threads:[~2026-03-11 18:56 UTC | newest]
Thread overview: 67+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-04-04 05:06 Re: Refactor handling of database attributes between pg_dump and pg_dumpall Haribabu Kommi <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2021-03-04 09:25 [PATCH 2/8] Expand the external data before forming the tuple Dilip Kumar <[email protected]>
2026-02-25 11:53 Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Álvaro Herrera <[email protected]>
2026-02-25 21:19 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Euler Taveira <[email protected]>
2026-02-27 12:22 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-04 08:39 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Japin Li <[email protected]>
2026-03-04 09:30 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Japin Li <[email protected]>
2026-03-04 12:59 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-04 14:01 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Japin Li <[email protected]>
2026-03-05 09:50 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-05 14:45 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Rafia Sabih <[email protected]>
2026-03-05 16:15 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-06 07:37 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Rafia Sabih <[email protected]>
2026-03-06 11:51 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-06 14:43 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Japin Li <[email protected]>
2026-03-09 12:08 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-10 10:16 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Kirill Reshke <[email protected]>
2026-03-10 13:44 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
2026-03-10 22:05 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Zsolt Parragi <[email protected]>
2026-03-11 18:56 ` Re: [PATCH] Add pg_get_database_ddl() function to reconstruct CREATE DATABASE statement Akshay Joshi <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox