Skip to content

Commit f62ea39

Browse files
committed
Add pg_get_policy_ddl() function to reconstruct CREATE POLICY statements.
This patch introduces a new system function: pg_get_policy_ddl(table regclass, policy_name name, VARIADIC options text[]) RETURNS setof text which reconstructs the CREATE POLICY statement for the named row-level security policy on the specified table. The result is returned as a single row.   The supported option is: pretty (boolean) - format the output for readability. Usage examples: -- non-pretty formatted DDL (default) SELECT * FROM pg_get_policy_ddl('rls_table', 'pol1'); SELECT * FROM pg_get_policy_ddl(16564, 'pol1'); -- pretty formatted DDL SELECT * FROM pg_get_policy_ddl('rls_table', 'pol1', 'pretty', 'true'); SELECT * FROM pg_get_policy_ddl(16564, 'pol1', 'pretty', 'true'); Reference: PG-163 Author: Akshay Joshi <akshay.joshi@enterprisedb.com>
1 parent e2b3573 commit f62ea39

5 files changed

Lines changed: 607 additions & 0 deletions

File tree

doc/src/sgml/func/func-info.sgml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3961,6 +3961,26 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
39613961
<literal>TABLESPACE</literal>.
39623962
</para></entry>
39633963
</row>
3964+
<row>
3965+
<entry role="func_table_entry"><para role="func_signature">
3966+
<indexterm>
3967+
<primary>pg_get_policy_ddl</primary>
3968+
</indexterm>
3969+
<function>pg_get_policy_ddl</function>
3970+
( <parameter>table</parameter> <type>regclass</type>,
3971+
<parameter>policy_name</parameter> <type>name</type>
3972+
<optional>, <literal>VARIADIC</literal> <parameter>options</parameter>
3973+
<type>text</type> </optional> )
3974+
<returnvalue>setof text</returnvalue>
3975+
</para>
3976+
<para>
3977+
Reconstructs the <command>CREATE POLICY</command> statement for the
3978+
named row-level security policy on the specified table. The result
3979+
is returned as a single row.
3980+
The following option is supported: <literal>pretty</literal>
3981+
(boolean) for formatted output.
3982+
</para></entry>
3983+
</row>
39643984
</tbody>
39653985
</tgroup>
39663986
</table>

src/backend/utils/adt/ddlutils.c

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "catalog/pg_collation.h"
2727
#include "catalog/pg_database.h"
2828
#include "catalog/pg_db_role_setting.h"
29+
#include "catalog/pg_policy.h"
2930
#include "catalog/pg_tablespace.h"
3031
#include "commands/tablespace.h"
3132
#include "common/relpath.h"
@@ -86,6 +87,9 @@ static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner
8687
static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull);
8788
static List *pg_get_database_ddl_internal(Oid dbid, bool pretty,
8889
bool no_owner, bool no_tablespace);
90+
static List *pg_get_policy_ddl_internal(Oid tableID, const char *policyName,
91+
bool pretty);
92+
static const char *get_policy_cmd_name(char cmd);
8993

9094

9195
/*
@@ -1185,3 +1189,261 @@ pg_get_database_ddl(PG_FUNCTION_ARGS)
11851189
SRF_RETURN_DONE(funcctx);
11861190
}
11871191
}
1192+
1193+
/*
1194+
* get_policy_cmd_name
1195+
* Map a pg_policy.polcmd char to its SQL keyword.
1196+
*/
1197+
static const char *
1198+
get_policy_cmd_name(char cmd)
1199+
{
1200+
switch (cmd)
1201+
{
1202+
case '*':
1203+
return "ALL";
1204+
case ACL_SELECT_CHR:
1205+
return "SELECT";
1206+
case ACL_INSERT_CHR:
1207+
return "INSERT";
1208+
case ACL_UPDATE_CHR:
1209+
return "UPDATE";
1210+
case ACL_DELETE_CHR:
1211+
return "DELETE";
1212+
default:
1213+
elog(ERROR, "unrecognized policy command: %d", (int) cmd);
1214+
}
1215+
}
1216+
1217+
/*
1218+
* pg_get_policy_ddl_internal
1219+
* Generate the DDL statement to recreate a row-level security policy.
1220+
*
1221+
* Returns a List containing a single palloc'd string with the CREATE POLICY
1222+
* statement. Returning a List keeps the calling convention consistent with
1223+
* the rest of the pg_get_*_ddl family even though only one row is produced.
1224+
*/
1225+
static List *
1226+
pg_get_policy_ddl_internal(Oid tableID, const char *policyName, bool pretty)
1227+
{
1228+
Relation pgPolicyRel;
1229+
HeapTuple tuplePolicy;
1230+
Form_pg_policy policyForm;
1231+
ScanKeyData skey[2];
1232+
SysScanDesc sscan;
1233+
StringInfoData buf;
1234+
Datum valueDatum;
1235+
bool attrIsNull;
1236+
char *targetTable;
1237+
List *statements = NIL;
1238+
1239+
/* Validate that the relation exists */
1240+
{
1241+
char *relname = get_rel_name(tableID);
1242+
char *nspname;
1243+
1244+
if (relname == NULL)
1245+
ereport(ERROR,
1246+
(errcode(ERRCODE_UNDEFINED_TABLE),
1247+
errmsg("relation with OID %u does not exist", tableID)));
1248+
1249+
nspname = get_namespace_name(get_rel_namespace(tableID));
1250+
if (nspname == NULL)
1251+
ereport(ERROR,
1252+
(errcode(ERRCODE_UNDEFINED_SCHEMA),
1253+
errmsg("schema for relation with OID %u does not exist",
1254+
tableID)));
1255+
1256+
targetTable = quote_qualified_identifier(nspname, relname);
1257+
pfree(relname);
1258+
pfree(nspname);
1259+
}
1260+
1261+
pgPolicyRel = table_open(PolicyRelationId, AccessShareLock);
1262+
1263+
/* Set key - policy's relation id. */
1264+
ScanKeyInit(&skey[0],
1265+
Anum_pg_policy_polrelid,
1266+
BTEqualStrategyNumber, F_OIDEQ,
1267+
ObjectIdGetDatum(tableID));
1268+
1269+
/* Set key - policy's name. */
1270+
ScanKeyInit(&skey[1],
1271+
Anum_pg_policy_polname,
1272+
BTEqualStrategyNumber, F_NAMEEQ,
1273+
CStringGetDatum(policyName));
1274+
1275+
sscan = systable_beginscan(pgPolicyRel,
1276+
PolicyPolrelidPolnameIndexId, true, NULL, 2,
1277+
skey);
1278+
1279+
tuplePolicy = systable_getnext(sscan);
1280+
if (!HeapTupleIsValid(tuplePolicy))
1281+
ereport(ERROR,
1282+
(errcode(ERRCODE_UNDEFINED_OBJECT),
1283+
errmsg("policy \"%s\" for table \"%s\" does not exist",
1284+
policyName, targetTable)));
1285+
1286+
policyForm = (Form_pg_policy) GETSTRUCT(tuplePolicy);
1287+
1288+
initStringInfo(&buf);
1289+
1290+
/* Build the CREATE POLICY statement */
1291+
appendStringInfo(&buf, "CREATE POLICY %s ON %s",
1292+
quote_identifier(policyName),
1293+
targetTable);
1294+
1295+
/*
1296+
* Emit AS RESTRICTIVE only when it differs from the default (PERMISSIVE).
1297+
*/
1298+
if (!policyForm->polpermissive)
1299+
append_ddl_option(&buf, pretty, 4, "AS RESTRICTIVE");
1300+
1301+
/*
1302+
* Emit FOR <cmd> only when it differs from the default (ALL, encoded as
1303+
* '*').
1304+
*/
1305+
if (policyForm->polcmd != '*')
1306+
append_ddl_option(&buf, pretty, 4, "FOR %s",
1307+
get_policy_cmd_name(policyForm->polcmd));
1308+
1309+
/*
1310+
* Emit TO <roles> only when it differs from the default (PUBLIC). PUBLIC
1311+
* is encoded in polroles as a single InvalidOid element, so we omit the
1312+
* clause whenever every entry is InvalidOid.
1313+
*/
1314+
valueDatum = heap_getattr(tuplePolicy,
1315+
Anum_pg_policy_polroles,
1316+
RelationGetDescr(pgPolicyRel),
1317+
&attrIsNull);
1318+
if (!attrIsNull)
1319+
{
1320+
ArrayType *policy_roles = DatumGetArrayTypePCopy(valueDatum);
1321+
int nitems = ARR_DIMS(policy_roles)[0];
1322+
Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
1323+
StringInfoData role_names;
1324+
1325+
initStringInfo(&role_names);
1326+
1327+
for (int i = 0; i < nitems; i++)
1328+
{
1329+
if (OidIsValid(roles[i]))
1330+
{
1331+
char *rolename = GetUserNameFromId(roles[i], false);
1332+
1333+
if (role_names.len > 0)
1334+
appendStringInfoString(&role_names, ", ");
1335+
appendStringInfoString(&role_names, quote_identifier(rolename));
1336+
}
1337+
}
1338+
1339+
if (role_names.len > 0)
1340+
append_ddl_option(&buf, pretty, 4, "TO %s", role_names.data);
1341+
1342+
pfree(role_names.data);
1343+
pfree(policy_roles);
1344+
}
1345+
1346+
/* USING expression */
1347+
valueDatum = heap_getattr(tuplePolicy,
1348+
Anum_pg_policy_polqual,
1349+
RelationGetDescr(pgPolicyRel),
1350+
&attrIsNull);
1351+
if (!attrIsNull)
1352+
{
1353+
Datum expr;
1354+
1355+
expr = DirectFunctionCall3(pg_get_expr_ext,
1356+
valueDatum,
1357+
ObjectIdGetDatum(policyForm->polrelid),
1358+
BoolGetDatum(pretty));
1359+
append_ddl_option(&buf, pretty, 4, "USING (%s)",
1360+
TextDatumGetCString(expr));
1361+
}
1362+
1363+
/* WITH CHECK expression */
1364+
valueDatum = heap_getattr(tuplePolicy,
1365+
Anum_pg_policy_polwithcheck,
1366+
RelationGetDescr(pgPolicyRel),
1367+
&attrIsNull);
1368+
if (!attrIsNull)
1369+
{
1370+
Datum expr;
1371+
1372+
expr = DirectFunctionCall3(pg_get_expr_ext,
1373+
valueDatum,
1374+
ObjectIdGetDatum(policyForm->polrelid),
1375+
BoolGetDatum(pretty));
1376+
append_ddl_option(&buf, pretty, 4, "WITH CHECK (%s)",
1377+
TextDatumGetCString(expr));
1378+
}
1379+
1380+
appendStringInfoChar(&buf, ';');
1381+
1382+
statements = lappend(statements, pstrdup(buf.data));
1383+
1384+
systable_endscan(sscan);
1385+
table_close(pgPolicyRel, AccessShareLock);
1386+
pfree(buf.data);
1387+
1388+
return statements;
1389+
}
1390+
1391+
/*
1392+
* pg_get_policy_ddl
1393+
* Return DDL to recreate a row-level security policy as a single text row.
1394+
*/
1395+
Datum
1396+
pg_get_policy_ddl(PG_FUNCTION_ARGS)
1397+
{
1398+
FuncCallContext *funcctx;
1399+
List *statements;
1400+
1401+
if (SRF_IS_FIRSTCALL())
1402+
{
1403+
MemoryContext oldcontext;
1404+
Oid tableID;
1405+
Name policyName;
1406+
DdlOption opts[] = {
1407+
{"pretty", DDL_OPT_BOOL},
1408+
};
1409+
1410+
funcctx = SRF_FIRSTCALL_INIT();
1411+
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1412+
1413+
if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
1414+
{
1415+
MemoryContextSwitchTo(oldcontext);
1416+
SRF_RETURN_DONE(funcctx);
1417+
}
1418+
1419+
tableID = PG_GETARG_OID(0);
1420+
policyName = PG_GETARG_NAME(1);
1421+
1422+
parse_ddl_options(fcinfo, 2, opts, lengthof(opts));
1423+
1424+
statements = pg_get_policy_ddl_internal(tableID,
1425+
NameStr(*policyName),
1426+
opts[0].isset && opts[0].boolval);
1427+
funcctx->user_fctx = statements;
1428+
funcctx->max_calls = list_length(statements);
1429+
1430+
MemoryContextSwitchTo(oldcontext);
1431+
}
1432+
1433+
funcctx = SRF_PERCALL_SETUP();
1434+
statements = (List *) funcctx->user_fctx;
1435+
1436+
if (funcctx->call_cntr < funcctx->max_calls)
1437+
{
1438+
char *stmt;
1439+
1440+
stmt = list_nth(statements, funcctx->call_cntr);
1441+
1442+
SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
1443+
}
1444+
else
1445+
{
1446+
list_free_deep(statements);
1447+
SRF_RETURN_DONE(funcctx);
1448+
}
1449+
}

src/include/catalog/pg_proc.dat

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8615,6 +8615,14 @@
86158615
proargtypes => 'regdatabase text', proallargtypes => '{regdatabase,text}',
86168616
proargmodes => '{i,v}', proargdefaults => '{NULL}',
86178617
prosrc => 'pg_get_database_ddl' },
8618+
{ oid => '6517', descr => 'get DDL to recreate a row-level security policy',
8619+
proname => 'pg_get_policy_ddl', prorows => '1', provariadic => 'text',
8620+
proisstrict => 'f', proretset => 't', provolatile => 's',
8621+
pronargdefaults => '1', prorettype => 'text',
8622+
proargtypes => 'regclass name text',
8623+
proallargtypes => '{regclass,name,text}',
8624+
proargmodes => '{i,i,v}', proargdefaults => '{NULL}',
8625+
prosrc => 'pg_get_policy_ddl' },
86188626
{ oid => '2509',
86198627
descr => 'deparse an encoded expression with pretty-print option',
86208628
proname => 'pg_get_expr', provolatile => 's', prorettype => 'text',

0 commit comments

Comments
 (0)