Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions doc/src/sgml/func/func-info.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -3797,4 +3797,59 @@ 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>databaseID</parameter> <type>regdatabase</type>, <optional> <parameter>pretty</parameter> <type>boolean</type> </optional> )
<returnvalue>text</returnvalue>
</para>
<para>
Reconstructs the <command>CREATE DATABASE</command> statement from the
system catalogs for a specified database name or database oid. The
result is a comprehensive <command>CREATE DATABASE</command> statement.
</para></entry>
</row>
</tbody>
</tgroup>
</table>

<para>
Most of the functions that reconstruct (decompile) database objects have an
optional <parameter>pretty</parameter> flag, which if
<literal>true</literal> causes the result to be
<quote>pretty-printed</quote>. Pretty-printing adds tab character and new
line character for legibility. Passing <literal>false</literal> for the
<parameter>pretty</parameter> parameter yields the same result as omitting
the parameter.
</para>

</sect2>

</sect1>
6 changes: 6 additions & 0 deletions src/backend/catalog/system_functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,12 @@ LANGUAGE INTERNAL
STRICT VOLATILE PARALLEL UNSAFE
AS 'pg_replication_origin_session_setup';

CREATE OR REPLACE FUNCTION
pg_get_database_ddl(databaseID regdatabase, pretty bool DEFAULT false)
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
Expand Down
189 changes: 189 additions & 0 deletions src/backend/utils/adt/ruleutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -57,6 +58,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"
Expand Down Expand Up @@ -94,6 +96,10 @@
((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

Expand Down Expand Up @@ -546,6 +552,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 nTabChars,
const char *fmt,...) pg_attribute_printf(4, 5);
static char *pg_get_database_ddl_worker(Oid dbOid, int prettyFlags);

#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")

Expand Down Expand Up @@ -13743,3 +13754,181 @@ 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 tabs (\t) and
* newlines (\n).
* nTabChars - indent with specified number of tab characters.
* fmt - printf-style format string used by appendStringInfoVA.
*/
static void
get_formatted_string(StringInfo buf, int prettyFlags, int nTabChars, const char *fmt,...)
{
va_list args;

if (prettyFlags & PRETTYFLAG_INDENT)
{
appendStringInfoChar(buf, '\n');
/* Indent with tabs */
for (int i = 0; i < nTabChars; i++)
{
appendStringInfoChar(buf, '\t');
}
}
else
appendStringInfoChar(buf, ' ');

va_start(args, fmt);
appendStringInfoVA(buf, fmt, args);
va_end(args);
}

/*
* pg_get_database_ddl
*
* Generate a CREATE DATABASE statement for the specified database name or oid.
*
* databaseID - OID/Name of the database for which to generate the DDL.
* pretty - If true, format the DDL with indentation and line breaks.
*/
Datum
pg_get_database_ddl(PG_FUNCTION_ARGS)
{
Oid dbOid = PG_GETARG_OID(0);
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
char *res;

prettyFlags = GET_DDL_PRETTY_FLAGS(pretty);
res = pg_get_database_ddl_worker(dbOid, prettyFlags);

if (res == NULL)
PG_RETURN_NULL();

PG_RETURN_TEXT_P(string_to_text(res));
}

static char *
pg_get_database_ddl_worker(Oid dbOid, int prettyFlags)
{
char *dbOwner = NULL;
bool attrIsNull;
Datum dbValue;
HeapTuple tupleDatabase;
Form_pg_database dbForm;
StringInfoData buf;
AclResult aclresult;

/*
* User must have connect privilege for target database.
*/
aclresult = object_aclcheck(DatabaseRelationId, dbOid, 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(dbOid));
}

/* Look up the database in pg_database */
tupleDatabase = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbOid));
if (!HeapTupleIsValid(tupleDatabase))
ereport(ERROR,
errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("database with oid %u does not exist", dbOid));

dbForm = (Form_pg_database) GETSTRUCT(tupleDatabase);

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, prettyFlags, 1, "WITH OWNER = %s",
quote_identifier(dbOwner));

if (dbForm->encoding != 0)
get_formatted_string(&buf, prettyFlags, 2, "ENCODING = %s",
quote_identifier(pg_encoding_to_char(dbForm->encoding)));

/* Fetch the value of LC_COLLATE */
dbValue = SysCacheGetAttr(DATABASEOID, tupleDatabase,
Anum_pg_database_datcollate, &attrIsNull);
if (!attrIsNull)
get_formatted_string(&buf, prettyFlags, 2, "LC_COLLATE = %s",
quote_identifier(TextDatumGetCString(dbValue)));

/* Fetch the value of LC_CTYPE */
dbValue = SysCacheGetAttr(DATABASEOID, tupleDatabase,
Anum_pg_database_datctype, &attrIsNull);
if (!attrIsNull)
get_formatted_string(&buf, prettyFlags, 2, "LC_CTYPE = %s",
quote_identifier(TextDatumGetCString(dbValue)));

/* Fetch the value of LOCALE */
dbValue = SysCacheGetAttr(DATABASEOID, tupleDatabase,
Anum_pg_database_datlocale, &attrIsNull);
if (!attrIsNull && dbForm->datlocprovider == COLLPROVIDER_BUILTIN)
get_formatted_string(&buf, prettyFlags, 2, "BUILTIN_LOCALE = %s",
quote_identifier(TextDatumGetCString(dbValue)));
else if (!attrIsNull && dbForm->datlocprovider == COLLPROVIDER_ICU)
get_formatted_string(&buf, prettyFlags, 2, "ICU_LOCALE = %s",
quote_identifier(TextDatumGetCString(dbValue)));

/* Fetch the value of ICU_RULES */
dbValue = SysCacheGetAttr(DATABASEOID, tupleDatabase,
Anum_pg_database_daticurules, &attrIsNull);
if (!attrIsNull && dbForm->datlocprovider == COLLPROVIDER_ICU)
get_formatted_string(&buf, prettyFlags, 2, "ICU_RULES = %s",
quote_identifier(TextDatumGetCString(dbValue)));

/* Fetch the value of COLLATION_VERSION */
dbValue = SysCacheGetAttr(DATABASEOID, tupleDatabase,
Anum_pg_database_datcollversion, &attrIsNull);
if (!attrIsNull)
get_formatted_string(&buf, prettyFlags, 2, "COLLATION_VERSION = %s",
quote_identifier(TextDatumGetCString(dbValue)));

/* Set the appropriate LOCALE_PROVIDER */
if (dbForm->datlocprovider == COLLPROVIDER_BUILTIN)
get_formatted_string(&buf, prettyFlags, 2, "LOCALE_PROVIDER = 'builtin'");
else if (dbForm->datlocprovider == COLLPROVIDER_ICU)
get_formatted_string(&buf, prettyFlags, 2, "LOCALE_PROVIDER = 'icu'");
else
get_formatted_string(&buf, prettyFlags, 2, "LOCALE_PROVIDER = 'libc'");

/* Get the tablespace name respective to the given tablespace oid */
if (OidIsValid(dbForm->dattablespace))
{
char *dbTablespace = get_tablespace_name(dbForm->dattablespace);

if (dbTablespace)
get_formatted_string(&buf, prettyFlags, 2, "TABLESPACE = %s",
quote_identifier(dbTablespace));
}

get_formatted_string(&buf, prettyFlags, 2, "ALLOW_CONNECTIONS = %s",
dbForm->datallowconn ? "true" : "false");

get_formatted_string(&buf, prettyFlags, 2, "CONNECTION LIMIT = %d",
dbForm->datconnlimit);

if (dbForm->datistemplate)
get_formatted_string(&buf, prettyFlags, 2, "IS_TEMPLATE = %s",
dbForm->datistemplate ? "true" : "false");

appendStringInfoChar(&buf, ';');

ReleaseSysCache(tupleDatabase);

return buf.data;
}
3 changes: 3 additions & 0 deletions src/include/catalog/pg_proc.dat
Original file line number Diff line number Diff line change
Expand Up @@ -4021,6 +4021,9 @@
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', prorettype => 'text',
proargtypes => 'regdatabase bool', prosrc => 'pg_get_database_ddl' },

{ oid => '1686', descr => 'list of SQL keywords',
proname => 'pg_get_keywords', procost => '10', prorows => '500',
Expand Down
77 changes: 77 additions & 0 deletions src/test/regress/expected/database.out
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
--
-- Reconsturct 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 LC_COLLATE assignments
cleaned_ddl := regexp_replace(
ddl_input,
'\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;
Expand All @@ -16,6 +62,37 @@ 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', false);
ERROR: database "regression_database" does not exist
LINE 1: SELECT pg_get_database_ddl('regression_database', false);
^
-- Test NULL value
SELECT pg_get_database_ddl(NULL);
pg_get_database_ddl
---------------------

(1 row)

-- Without pretty
SELECT ddl_filter(pg_get_database_ddl('regression_utf8'));
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 Pretty formatted
\pset format unaligned
SELECT ddl_filter(pg_get_database_ddl('regression_utf8', 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)
DROP DATABASE regression_utf8;
DROP ROLE regress_datdba_before;
DROP ROLE regress_datdba_after;
Loading