|
26 | 26 | #include "catalog/pg_collation.h" |
27 | 27 | #include "catalog/pg_database.h" |
28 | 28 | #include "catalog/pg_db_role_setting.h" |
| 29 | +#include "catalog/pg_policy.h" |
29 | 30 | #include "catalog/pg_tablespace.h" |
30 | 31 | #include "commands/tablespace.h" |
31 | 32 | #include "common/relpath.h" |
@@ -86,6 +87,9 @@ static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner |
86 | 87 | static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull); |
87 | 88 | static List *pg_get_database_ddl_internal(Oid dbid, bool pretty, |
88 | 89 | 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); |
89 | 93 |
|
90 | 94 |
|
91 | 95 | /* |
@@ -1185,3 +1189,261 @@ pg_get_database_ddl(PG_FUNCTION_ARGS) |
1185 | 1189 | SRF_RETURN_DONE(funcctx); |
1186 | 1190 | } |
1187 | 1191 | } |
| 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 | +} |
0 commit comments