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
51 changes: 51 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,55 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}

</sect2>

<sect2 id="functions-get-object-ddl">
<title>Get Object DDL Functions</title>

<para>
The functions shown in <xref linkend="functions-get-object-ddl-table"/>
print the DDL statements for various database objects.
(This is a decompiled reconstruction, not the original text
of the command.)
</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_subscription_ddl</primary>
</indexterm>
<function>pg_get_subscription_ddl</function> ( <parameter>subscription</parameter> <type>text</type> )
<returnvalue>text</returnvalue>
</para>
<para>
Reconstructs the creating command for a subscription.
The result is a complete <command>CREATE SUBSCRIPTION</command>
statement. The <literal>connect</literal> option set to
<literal>false</literal>.
</para>
<para>
This function is restricted to users that have the
<literal>pg_read_all_data</literal> and/or
<literal>pg_create_subscription</literal> privilege.
</para></entry>
</row>
</tbody>
</tgroup>
</table>

</sect2>

</sect1>
4 changes: 1 addition & 3 deletions src/backend/catalog/pg_subscription.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
#include "utils/rel.h"
#include "utils/syscache.h"

static List *textarray_to_stringlist(ArrayType *textarray);

/*
* Add a comma-separated list of publication names to the 'dest' string.
*/
Expand Down Expand Up @@ -240,7 +238,7 @@ DisableSubscription(Oid subid)
*
* Note: the resulting list of strings is pallocated here.
*/
static List *
List *
textarray_to_stringlist(ArrayType *textarray)
{
Datum *elems;
Expand Down
177 changes: 177 additions & 0 deletions src/backend/utils/adt/ruleutils.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "catalog/pg_partitioned_table.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
Expand All @@ -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 @@ -13715,3 +13717,178 @@ get_range_partbound_string(List *bound_datums)

return buf->data;
}

/*
* pg_get_subscription_ddl
* Get CREATE SUBSCRIPTION statement for the given subscription
*/
Datum
pg_get_subscription_ddl(PG_FUNCTION_ARGS)
{
char *subname = text_to_cstring(PG_GETARG_TEXT_P(0));
StringInfo pubnames;
StringInfoData buf;
HeapTuple tup;
char *conninfo;
List *publist;
Datum datum;
bool isnull;

/*
* To prevent unprivileged users from initiating unauthorized network
* connections, dumping subscription creation is restricted. A user must
* be specifically authorized (via the appropriate role privilege) to
* create subscriptions and/or to read all data.
*/
if (!(has_privs_of_role(GetUserId(), ROLE_PG_CREATE_SUBSCRIPTION) ||
has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_DATA)))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to get the create subscription ddl"),
errdetail("Only roles with privileges of the \"%s\" and/or \"%s\" role may get ddl.",
"pg_create_subscription", "pg_read_all_data")));

/* Look up the subscription in pg_subscription */
tup = SearchSysCache2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
CStringGetDatum(subname));
if (!HeapTupleIsValid(tup))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg("subscription \"%s\" does not exist", subname)));

initStringInfo(&buf);

/* Build the CREATE SUBSCRIPTION statement */
appendStringInfo(&buf, "CREATE SUBSCRIPTION %s ",
quote_identifier(subname));

/* Get conninfo */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subconninfo);
conninfo = TextDatumGetCString(datum);

/* Append connection info to the CREATE SUBSCRIPTION statement */
appendStringInfo(&buf, "CONNECTION \'%s\'", conninfo);

/* Build list of quoted publications and append them to query */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subpublications);
publist = textarray_to_stringlist(DatumGetArrayTypeP(datum));
pubnames = makeStringInfo();
GetPublicationsStr(publist, pubnames, false);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pubnames be passed by reference?

appendStringInfo(&buf, " PUBLICATION %s", pubnames->data);

/*
* Add options using WITH clause. The 'connect' option value given at the
* time of subscription creation is not available in the catalog. When
* creating a subscription, the remote host is not reachable or in an
* unclear state, in that case, the subscription can be created using
* 'connect = false' option. This is what pg_dump uses.
*
* The status or value of the options 'create_slot' and 'copy_data' not
* available in the catalog table. We can use default values i.e. TRUE
* for both. This is what pg_dump uses.
*/
appendStringInfoString(&buf, " WITH (connect = false");

/* Get slotname */
datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subslotname,
&isnull);
if (!isnull)
appendStringInfo(&buf, ", slot_name = \'%s\'",
NameStr(*DatumGetName(datum)));
else
{
appendStringInfoString(&buf, ", slot_name = none");
/* Setting slot_name to none must set create_slot to false */
appendStringInfoString(&buf, ", create_slot = false");
}

/* Get enabled option */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subenabled);
/* Setting 'slot_name' to none must set 'enabled' to false as well */
if (!DatumGetBool(datum) || isnull)
appendStringInfoString(&buf, ", enabled = false");
else
appendStringInfoString(&buf, ", enabled = true");

/* Get binary option */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subbinary);
appendStringInfo(&buf, ", binary = %s",
DatumGetBool(datum) ? "true" : "false");

/* Get streaming option */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_substream);
if (DatumGetChar(datum) == LOGICALREP_STREAM_OFF)
appendStringInfoString(&buf, ", streaming = off");
else if (DatumGetChar(datum) == LOGICALREP_STREAM_ON)
appendStringInfoString(&buf, ", streaming = on");
else
appendStringInfoString(&buf, ", streaming = parallel");

/* Get sync commit option */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subsynccommit);
appendStringInfo(&buf, ", synchronous_commit = %s",
TextDatumGetCString(datum));

/* Get two-phase commit option */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subtwophasestate);
if (DatumGetChar(datum) == LOGICALREP_TWOPHASE_STATE_DISABLED)
appendStringInfoString(&buf, ", two_phase = off");
else
appendStringInfoString(&buf, ", two_phase = on");

/* Disable on error? */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subdisableonerr);
appendStringInfo(&buf, ", disable_on_error = %s",
DatumGetBool(datum) ? "on" : "off");

/* Password required? */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subpasswordrequired);
appendStringInfo(&buf, ", password_required = %s",
DatumGetBool(datum) ? "on" : "off");

/* Run as owner? */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subrunasowner);
appendStringInfo(&buf, ", run_as_owner = %s",
DatumGetBool(datum) ? "on" : "off");

/* Get origin */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_suborigin);
appendStringInfo(&buf, ", origin = %s", TextDatumGetCString(datum));

/* Failover? */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subfailover);
appendStringInfo(&buf, ", failover = %s",
DatumGetBool(datum) ? "on" : "off");

/* Retain dead tuples? */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_subretaindeadtuples);
appendStringInfo(&buf, ", retain_dead_tuples = %s",
DatumGetBool(datum) ? "on" : "off");

/* Max retention duration */
datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
Anum_pg_subscription_submaxretention);
appendStringInfo(&buf, ", max_retention_duration = %lu",
Int32GetDatum(datum));

/* Finally close parenthesis and add semicolon to the statement */
appendStringInfoString(&buf, ");");

ReleaseSysCache(tup);

PG_RETURN_TEXT_P(string_to_text(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 @@ -3993,6 +3993,9 @@
{ oid => '1387', descr => 'constraint description',
proname => 'pg_get_constraintdef', provolatile => 's', prorettype => 'text',
proargtypes => 'oid', prosrc => 'pg_get_constraintdef' },
{ oid => '8001', descr => 'get CREATE statement for subscription',
proname => 'pg_get_subscription_ddl', prorettype => 'text',
proargtypes => 'text', prosrc => 'pg_get_subscription_ddl' },
{ oid => '1716', descr => 'deparse an encoded expression',
proname => 'pg_get_expr', provolatile => 's', prorettype => 'text',
proargtypes => 'pg_node_tree oid', prosrc => 'pg_get_expr' },
Expand Down
2 changes: 2 additions & 0 deletions src/include/catalog/pg_subscription.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "catalog/pg_subscription_d.h" /* IWYU pragma: export */
#include "lib/stringinfo.h"
#include "nodes/pg_list.h"
#include "utils/array.h"

/* ----------------
* pg_subscription definition. cpp turns this into
Expand Down Expand Up @@ -207,5 +208,6 @@ extern int CountDBSubscriptions(Oid dbid);

extern void GetPublicationsStr(List *publications, StringInfo dest,
bool quote_literal);
extern List *textarray_to_stringlist(ArrayType *textarray);

#endif /* PG_SUBSCRIPTION_H */
85 changes: 85 additions & 0 deletions src/test/regress/expected/subscription_ddl.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
--
-- Get CREATE SUBSCRIPTION statement
--
CREATE ROLE createsub_role LOGIN;
CREATE ROLE readalldata_role LOGIN;
-- Create subscription with minimal options
CREATE SUBSCRIPTION testsub1 CONNECTION 'dbname=db_doesnotexist'
PUBLICATION testpub1 WITH (connect=false);
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
-- Check that the subscription ddl is correctly created
SELECT pg_get_subscription_ddl('testsub1');
pg_get_subscription_ddl
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE SUBSCRIPTION testsub1 CONNECTION 'dbname=db_doesnotexist' PUBLICATION "testpub1" WITH (connect = false, slot_name = 'testsub1', enabled = false, binary = false, streaming = parallel, synchronous_commit = off, two_phase = off, disable_on_error = off, password_required = on, run_as_owner = off, origin = any, failover = off, retain_dead_tuples = off, max_retention_duration = 0);
(1 row)

-- Create subscription with more options
CREATE SUBSCRIPTION "TestSubddL2" CONNECTION 'host=unknown user=dvd password=pass123'
PUBLICATION "testpub2", "TestPub3" WITH (connect=false, slot_name='slot1',
enabled=off);
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
SELECT pg_get_subscription_ddl('TestSubddL2');
pg_get_subscription_ddl
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE SUBSCRIPTION "TestSubddL2" CONNECTION 'host=unknown user=dvd password=pass123' PUBLICATION "testpub2", "TestPub3" WITH (connect = false, slot_name = 'slot1', enabled = false, binary = false, streaming = parallel, synchronous_commit = off, two_phase = off, disable_on_error = off, password_required = on, run_as_owner = off, origin = any, failover = off, retain_dead_tuples = off, max_retention_duration = 0);
(1 row)

-- Create subscription with all options
CREATE SUBSCRIPTION testsub3 CONNECTION 'host=unknown user=dvd password=pass12'
PUBLICATION testpub4 WITH (connect=false, slot_name=none, enabled=false,
create_slot=false, copy_data=false, binary=true, streaming=off,
synchronous_commit=local, two_phase=true, disable_on_error=true,
password_required=false, run_as_owner=true, origin=none, failover=true,
retain_dead_tuples=false, max_retention_duration=100);
NOTICE: max_retention_duration is ineffective when retain_dead_tuples is disabled
WARNING: subscription was created, but is not connected
HINT: To initiate replication, you must manually create the replication slot, enable the subscription, and refresh the subscription.
SELECT pg_get_subscription_ddl('testsub3');
pg_get_subscription_ddl
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE SUBSCRIPTION testsub3 CONNECTION 'host=unknown user=dvd password=pass12' PUBLICATION "testpub4" WITH (connect = false, slot_name = none, create_slot = false, enabled = false, binary = true, streaming = off, synchronous_commit = local, two_phase = on, disable_on_error = on, password_required = off, run_as_owner = on, origin = none, failover = on, retain_dead_tuples = off, max_retention_duration = 100);
(1 row)

-- Non-superusers and which don't have pg_create_subscription and/or
-- pg_read_all_data permission can't get ddl
SET SESSION AUTHORIZATION 'createsub_role';
SELECT pg_get_subscription_ddl('TestSubddL2');
ERROR: permission denied to get the create subscription ddl
DETAIL: Only roles with privileges of the "pg_create_subscription" and/or "pg_read_all_data" role may get ddl.
RESET SESSION AUTHORIZATION;
SET SESSION AUTHORIZATION 'readalldata_role';
SELECT pg_get_subscription_ddl('TestSubddL2');
ERROR: permission denied to get the create subscription ddl
DETAIL: Only roles with privileges of the "pg_create_subscription" and/or "pg_read_all_data" role may get ddl.
RESET SESSION AUTHORIZATION;
-- Administrators can change who can access this function
GRANT pg_create_subscription TO createsub_role;
GRANT pg_read_all_data TO readalldata_role;
SET SESSION AUTHORIZATION 'createsub_role';
SELECT pg_get_subscription_ddl('TestSubddL2');
pg_get_subscription_ddl
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE SUBSCRIPTION "TestSubddL2" CONNECTION 'host=unknown user=dvd password=pass123' PUBLICATION "testpub2", "TestPub3" WITH (connect = false, slot_name = 'slot1', enabled = false, binary = false, streaming = parallel, synchronous_commit = off, two_phase = off, disable_on_error = off, password_required = on, run_as_owner = off, origin = any, failover = off, retain_dead_tuples = off, max_retention_duration = 0);
(1 row)

RESET SESSION AUTHORIZATION;
SET SESSION AUTHORIZATION 'readalldata_role';
SELECT pg_get_subscription_ddl('TestSubddL2');
pg_get_subscription_ddl
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE SUBSCRIPTION "TestSubddL2" CONNECTION 'host=unknown user=dvd password=pass123' PUBLICATION "testpub2", "TestPub3" WITH (connect = false, slot_name = 'slot1', enabled = false, binary = false, streaming = parallel, synchronous_commit = off, two_phase = off, disable_on_error = off, password_required = on, run_as_owner = off, origin = any, failover = off, retain_dead_tuples = off, max_retention_duration = 0);
(1 row)

RESET SESSION AUTHORIZATION;
REVOKE pg_create_subscription FROM createsub_role;
REVOKE pg_read_all_data FROM readalldata_role;
ALTER SUBSCRIPTION testsub1 SET (slot_name=NONE);
DROP SUBSCRIPTION testsub1;
ALTER SUBSCRIPTION "TestSubddL2" SET (slot_name=NONE);
DROP SUBSCRIPTION "TestSubddL2";
DROP SUBSCRIPTION testsub3;
DROP ROLE createsub_role;
DROP ROLE readalldata_role;
Loading