diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 76d6405061..a6ca290cb3 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1941,8 +1941,9 @@
- relhasrowsecurity
+ relrowsecuritybool
+
True if table has row-security enabled; see
pg_rowsecurity catalog
@@ -5415,7 +5416,7 @@
- pg_class.relhasrowsecurity
+ pg_class.relrowsecurity
True if the table has row-security enabled.
Must be true if the table has a row-security policy in this catalog.
@@ -9228,10 +9229,10 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
True if table has (or once had) triggers
- hasrowsecurity
+ rowsecurityboolean
- pg_class.relhasrowsecurity
- True if table has row security enabled
+ pg_class.relrowsecurity
+ True if row security is enabled on the table
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 70e47aaa3a..949443931c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -5457,9 +5457,9 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
The allowed values of row_security> are
- on> (apply normally- not to superuser or table owner),
+ on> (apply normally - not to superuser or table owner),
off> (fail if row security would be applied), and
- force> (apply always- even to superuser and table owner).
+ force> (apply always - even to superuser and table owner).
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c07f5a203d..e5ee591051 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1508,6 +1508,174 @@ REVOKE ALL ON accounts FROM PUBLIC;
+
+ Row Security Policies
+
+
+ rowsecurity
+
+
+
+ rls
+
+
+
+ policies
+ policy
+
+
+
+ POLICY
+
+
+
+ In addition to the system available through
+ , tables can have row security policies
+ which limit the rows returned for normal queries and rows which can
+ be added through data modification commands. By default, tables do
+ not have any policies and all rows are visible and able to be added,
+ subject to the regular system. This is
+ also known to as Row Level Security.
+
+
+
+ When row security is enabled on a table with
+ , all normal access to the table
+ (excluding the owner) for selecting rows or adding rows must be through
+ a policy. If no policy exists for the table, a default-deny policy is
+ used and no rows are visible or can be added. Privileges which operate
+ at the whole table level such as TRUNCATE>, and
+ REFERENCES> are not subject to row security.
+
+
+
+ Row security policies can be specific to commands, or to roles, or to
+ both. The commands available are SELECT>, INSERT>,
+ UPDATE>, and DELETE>. Multiple roles can be
+ assigned to a given policy and normal role membership and inheiritance
+ rules apply.
+
+
+
+ To specify which rows are visible and what rows can be added to the
+ table with row security, an expression is required which returns a
+ boolean result. This expression will be evaluated for each row prior
+ to other conditionals or functions which are part of the query. The
+ one exception to this rule are leakproof functions,
+ which are guaranteed to not leak information. Two expressions may be
+ specified to provide independent control over the rows which are
+ visible and the rows which are allowed to be added. The expression
+ is run as part of the query and with the privileges of the user
+ running the query, however, security definer functions can be used in
+ the expression.
+
+
+
+ Enabling and disabling row security, as well as adding policies to a
+ table, is always the privilege of the owner only.
+
+
+
+ Policies are created using the
+ command, altered using the command,
+ and dropped using the command. To
+ enable and disable row security for a given table, use the
+ command.
+
+
+
+ The table owners and superusers bypass the row security system when
+ querying a table, by default. Row security can be enabled for
+ superusers and table owners by setting
+ to force. Any
+ user can request that row security be bypassed by setting
+ to off. If
+ the user does not have privileges to bypass row security when
+ querying a given table then an error will be returned instead. Other
+ users can be granted the ability to bypass the row security system
+ with the BYPASSRLS role attribute. This
+ attribute can only be set by a superuser.
+
+
+
+ Each policy has a name and multiple policies can be defined for a
+ table. As policies are table-specific, each policy for a table must
+ have a unique name. Different tables may have policies with the
+ same name.
+
+
+
+ When multiple policies apply to a given query, they are combined using
+ OR, similar to how a given role has the privileges
+ of all roles which they are a member of.
+
+
+
+ Referential integrity checks, such as unique or primary key constraints
+ and foreign key references, will bypass row security to ensure that
+ data integrity is maintained. Care must be taken when developing
+ schemas and row level policies to avoid a "covert channel" leak of
+ information through these referntial integrity checks.
+
+
+
+ To enable row security for a table,
+ the ALTER TABLE is used. For example, to enable
+ row level security for the table accounts, use:
+
+
+
+-- Create the table first
+CREATE TABLE accounts (manager text, company text, contact_email text);
+ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
+
+
+
+ To create a policy on the account relation to allow the managers role
+ to view the rows of their accounts, the CREATE POLICY
+ command can be used:
+
+
+
+CREATE POLICY account_managers ON accounts TO managers
+ USING (manager = current_user);
+
+
+
+ If no role is specified, or the special user name
+ PUBLIC is used, then the policy applies to all
+ users on the system. To allow all users to view their own row in
+ a user table, a simple policy can be used:
+
+
+
+CREATE POLICY user_policy ON users
+ USING (user = current_user);
+
+
+
+ To use a different policy for rows which are being added to the
+ table from those rows which are visible, the WITH CHECK clause
+ can be used. This would allow all users to view all rows in the
+ users table, but only modify their own:
+
+
+
+CREATE POLICY user_policy ON users
+ USING (true)
+ WITH CHECK (user = current_user);
+
+
+
+ Row security can be disabled with the ALTER TABLE
+ also. Note that disabling row security does not remove the
+ policies which are defined on the table, they are simply ignored
+ and all rows are visible and able to be added, subject to the
+ normal privileges system.
+
+
+
+
Schemas
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 1b35756c29..b5ef09e6a4 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -429,7 +429,7 @@ ALTER TABLE ALL IN TABLESPACE name
These forms control the application of row security policies belonging
to the table. If enabled and no policies exist for the table, then a
default-deny policy is applied. Note that policies can exist for a table
- even if row level security is disabled- in this case, the policies will
+ even if row level security is disabled - in this case, the policies will
NOT be applied and the policies will be ignored.
See also
.
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
index c6599eda1c..3c5bdc69cd 100644
--- a/doc/src/sgml/ref/create_policy.sgml
+++ b/doc/src/sgml/ref/create_policy.sgml
@@ -240,7 +240,7 @@ CREATE POLICY name ON
- DELETE>
+ UPDATE>
Using UPDATE for a policy means that it will apply
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index eabdc62f82..c92c6eef5d 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -687,6 +687,23 @@ PostgreSQL documentation
+
+
+
+
+ This option is relevant only when dumping the contents of a table
+ which has row security. By default, pg_dump will set
+ ROW_SECURITY to OFF, to ensure
+ that all data is dumped from the table. If the user does not have
+ sufficient privileges to bypass row security, then an error is thrown.
+ This parameter instructs pg_dump to set
+ row_security to 'ON' instead, allowing the user to dump the contents
+ of the table which they have access to.
+
+
+
+
+
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 4bc30ce679..9f8dc00480 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -490,6 +490,29 @@
+
+
+
+ This option is relevant only when restoring the contents of a table
+ which has row security. By default, pg_restore will set
+ ROW_SECURITY to OFF, to ensure
+ that all data is restored in to the table. If the user does not have
+ sufficient privileges to bypass row security, then an error is thrown.
+ This parameter instructs pg_restore to set
+ row_security to 'ON' instead, allowing the user to attempt to restore
+ the contents of the table with row security enabled. This may still
+ fail if the user does not have the right to insert the rows from the
+ dump into the table.
+
+
+
+ Note that this option currently also requires the dump be in INSERT
+ format as COPY TO does not support row security.
+
+
+
+
+
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8d9eeb9dd7..55c1e79563 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -799,7 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
- values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
+ values[Anum_pg_class_relrowsecurity - 1] = BoolGetDatum(rd_rel->relrowsecurity);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
values[Anum_pg_class_relreplident - 1] = CharGetDatum(rd_rel->relreplident);
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index f62ed2e17d..9d9d239484 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -119,7 +119,7 @@ CREATE VIEW pg_tables AS
C.relhasindex AS hasindexes,
C.relhasrules AS hasrules,
C.relhastriggers AS hastriggers,
- C.relhasrowsecurity AS hasrowsecurity
+ C.relrowsecurity AS rowsecurity
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
WHERE C.relkind = 'r';
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 0cfba566d0..6bff9500c6 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -108,7 +108,7 @@ parse_row_security_command(const char *cmd_name)
char cmd;
if (!cmd_name)
- elog(ERROR, "Unregonized command.");
+ elog(ERROR, "unregonized command");
if (strcmp(cmd_name, "all") == 0)
cmd = 0;
@@ -121,8 +121,7 @@ parse_row_security_command(const char *cmd_name)
else if (strcmp(cmd_name, "delete") == 0)
cmd = ACL_DELETE_CHR;
else
- elog(ERROR, "Unregonized command.");
- /* error unrecognized command */
+ elog(ERROR, "unregonized command");
return cmd;
}
@@ -422,8 +421,8 @@ RemovePolicyById(Oid policy_id)
heap_close(rel, AccessExclusiveLock);
/*
- * Note that, unlike some of the other flags in pg_class, relhasrowsecurity
- * is not just an indication of if policies exist. When relhasrowsecurity
+ * Note that, unlike some of the other flags in pg_class, relrowsecurity
+ * is not just an indication of if policies exist. When relrowsecurity
* is set (which can be done directly by the user or indirectly by creating
* a policy on the table), then all access to the relation must be through
* a policy. If no policy is defined for the relation then a default-deny
@@ -484,7 +483,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
if (rseccmd == ACL_INSERT_CHR && stmt->qual != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("Only WITH CHECK expression allowed for INSERT")));
+ errmsg("only WITH CHECK expression allowed for INSERT")));
/* Collect role ids */
@@ -731,7 +730,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
if (!HeapTupleIsValid(rsec_tuple))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("policy '%s' for does not exist on table %s",
+ errmsg("policy \"%s\" on table \"%s\" does not exist",
stmt->policy_name,
RelationGetRelationName(target_table))));
@@ -850,7 +849,7 @@ rename_policy(RenameStmt *stmt)
pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
- /* First pass- check for conflict */
+ /* First pass -- check for conflict */
/* Add key - row security relation id. */
ScanKeyInit(&skey[0],
@@ -868,7 +867,7 @@ rename_policy(RenameStmt *stmt)
RowSecurityRelidPolnameIndexId, true, NULL, 2,
skey);
- if (HeapTupleIsValid(rsec_tuple = systable_getnext(sscan)))
+ if (HeapTupleIsValid(systable_getnext(sscan)))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("row-policy \"%s\" for table \"%s\" already exists",
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0385404c57..cb16c53a60 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -10647,7 +10647,7 @@ ATExecEnableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
- ((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
+ ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = true;
simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */
@@ -10674,7 +10674,7 @@ ATExecDisableRowSecurity(Relation rel)
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
- ((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = false;
+ ((Form_pg_class) GETSTRUCT(tuple))->relrowsecurity = false;
simple_heap_update(pg_class, &tuple->t_self, tuple);
/* keep catalog indexes current */
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
index e1ccd1295e..bb95b36719 100644
--- a/src/backend/rewrite/rowsecurity.c
+++ b/src/backend/rewrite/rowsecurity.c
@@ -61,7 +61,7 @@ static void process_policies(List *policies, int rt_index,
Expr **final_qual,
Expr **final_with_check_qual,
bool *hassublinks);
-static bool check_role_for_policy(RowSecurityPolicy *policy, Oid user_id);
+static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id);
/*
* hook to allow extensions to apply their own security policy
@@ -177,7 +177,7 @@ prepend_row_security_policies(Query* root, RangeTblEntry* rte, int rt_index)
* all of them OR'd together. However, to avoid the situation of an
* extension granting more access to a table than the internal policies
* would allow, the extension's policies are AND'd with the internal
- * policies. In other words- extensions can only provide further
+ * policies. In other words - extensions can only provide further
* filtering of the result set (or further reduce the set of records
* allowed to be added).
*
@@ -305,7 +305,8 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
policy = (RowSecurityPolicy *) lfirst(item);
/* Always add ALL policies, if they exist. */
- if (policy->cmd == '\0' && check_role_for_policy(policy, user_id))
+ if (policy->cmd == '\0' &&
+ check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
/* Build the list of policies to return. */
@@ -313,23 +314,23 @@ pull_row_security_policies(CmdType cmd, Relation relation, Oid user_id)
{
case CMD_SELECT:
if (policy->cmd == ACL_SELECT_CHR
- && check_role_for_policy(policy, user_id))
+ && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_INSERT:
/* If INSERT then only need to add the WITH CHECK qual */
if (policy->cmd == ACL_INSERT_CHR
- && check_role_for_policy(policy, user_id))
+ && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_UPDATE:
if (policy->cmd == ACL_UPDATE_CHR
- && check_role_for_policy(policy, user_id))
+ && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
case CMD_DELETE:
if (policy->cmd == ACL_DELETE_CHR
- && check_role_for_policy(policy, user_id))
+ && check_role_for_policy(policy->roles, user_id))
policies = lcons(policy, policies);
break;
default:
@@ -473,7 +474,7 @@ check_enable_rls(Oid relid, Oid checkAsUser)
{
HeapTuple tuple;
Form_pg_class classform;
- bool relhasrowsecurity;
+ bool relrowsecurity;
Oid user_id = checkAsUser ? checkAsUser : GetUserId();
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
@@ -482,12 +483,12 @@ check_enable_rls(Oid relid, Oid checkAsUser)
classform = (Form_pg_class) GETSTRUCT(tuple);
- relhasrowsecurity = classform->relhasrowsecurity;
+ relrowsecurity = classform->relrowsecurity;
ReleaseSysCache(tuple);
/* Nothing to do if the relation does not have RLS */
- if (!relhasrowsecurity)
+ if (!relrowsecurity)
return RLS_NONE;
/*
@@ -537,19 +538,19 @@ check_enable_rls(Oid relid, Oid checkAsUser)
* check_role_for_policy -
* determines if the policy should be applied for the current role
*/
-bool
-check_role_for_policy(RowSecurityPolicy *policy, Oid user_id)
+static bool
+check_role_for_policy(ArrayType *policy_roles, Oid user_id)
{
int i;
- Oid *roles = (Oid *) ARR_DATA_PTR(policy->roles);
+ Oid *roles = (Oid *) ARR_DATA_PTR(policy_roles);
/* Quick fall-thru for policies applied to all roles */
if (roles[0] == ACL_ID_PUBLIC)
return true;
- for (i = 0; i < ARR_DIMS(policy->roles)[0]; i++)
+ for (i = 0; i < ARR_DIMS(policy_roles)[0]; i++)
{
- if (is_member_of_role(user_id, roles[i]))
+ if (has_privs_of_role(user_id, roles[i]))
return true;
}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index ed4a3769e4..c0156fab1f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2309,9 +2309,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
* have RLS enabled.
*/
if (!has_bypassrls_privilege(GetUserId()) &&
- ((pk_rel->rd_rel->relhasrowsecurity &&
+ ((pk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(pkrte->relid, GetUserId())) ||
- (fk_rel->rd_rel->relhasrowsecurity &&
+ (fk_rel->rd_rel->relrowsecurity &&
!pg_class_ownercheck(fkrte->relid, GetUserId()))))
return false;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index e7f7129bd9..c98e313288 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -847,6 +847,87 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
return true;
}
+/*
+ * equalPolicy
+ *
+ * Determine whether two policies are equivalent
+ */
+static bool
+equalPolicy(RowSecurityPolicy *policy1, RowSecurityPolicy *policy2)
+{
+ int i;
+ Oid *r1,
+ *r2;
+
+ if (policy1 != NULL)
+ {
+ if (policy2 == NULL)
+ return false;
+
+ if (policy1->rsecid != policy2->rsecid)
+ return false;
+ if (policy1->cmd != policy2->cmd)
+ return false;
+ if (policy1->hassublinks != policy2->hassublinks);
+ return false;
+ if (strcmp(policy1->policy_name,policy2->policy_name) != 0)
+ return false;
+ if (ARR_DIMS(policy1->roles)[0] != ARR_DIMS(policy2->roles)[0])
+ return false;
+
+ r1 = (Oid *) ARR_DATA_PTR(policy1->roles);
+ r2 = (Oid *) ARR_DATA_PTR(policy2->roles);
+
+ for (i = 0; i < ARR_DIMS(policy1->roles)[0]; i++)
+ {
+ if (r1[i] != r2[i])
+ return false;
+ }
+
+ if (!equal(policy1->qual, policy1->qual))
+ return false;
+ if (!equal(policy1->with_check_qual, policy2->with_check_qual))
+ return false;
+ }
+ else if (policy2 != NULL)
+ return false;
+
+ return true;
+}
+
+/*
+ * equalRSDesc
+ *
+ * Determine whether two RowSecurityDesc's are equivalent
+ */
+static bool
+equalRSDesc(RowSecurityDesc *rsdesc1, RowSecurityDesc *rsdesc2)
+{
+ ListCell *lc,
+ *rc;
+
+ if (rsdesc1 == NULL && rsdesc2 == NULL)
+ return true;
+
+ if ((rsdesc1 != NULL && rsdesc2 == NULL) ||
+ (rsdesc1 == NULL && rsdesc2 != NULL))
+ return false;
+
+ if (list_length(rsdesc1->policies) != list_length(rsdesc2->policies))
+ return false;
+
+ /* RelationBuildRowSecurity should build policies in order */
+ forboth(lc, rsdesc1->policies, rc, rsdesc2->policies)
+ {
+ RowSecurityPolicy *l = (RowSecurityPolicy *) lfirst(lc);
+ RowSecurityPolicy *r = (RowSecurityPolicy *) lfirst(rc);
+
+ if (!equalPolicy(l,r))
+ return false;
+ }
+
+ return false;
+}
/*
* RelationBuildDesc
@@ -967,7 +1048,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
else
relation->trigdesc = NULL;
- if (relation->rd_rel->relhasrowsecurity)
+ if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rsdesc = NULL;
@@ -2104,6 +2185,7 @@ RelationClearRelation(Relation relation, bool rebuild)
Oid save_relid = RelationGetRelid(relation);
bool keep_tupdesc;
bool keep_rules;
+ bool keep_policies;
/* Build temporary entry, but don't link it into hashtable */
newrel = RelationBuildDesc(save_relid, false);
@@ -2117,6 +2199,7 @@ RelationClearRelation(Relation relation, bool rebuild)
keep_tupdesc = equalTupleDescs(relation->rd_att, newrel->rd_att);
keep_rules = equalRuleLocks(relation->rd_rules, newrel->rd_rules);
+ keep_policies = equalRSDesc(relation->rsdesc, newrel->rsdesc);
/*
* Perform swapping of the relcache entry contents. Within this
@@ -2165,6 +2248,8 @@ RelationClearRelation(Relation relation, bool rebuild)
SWAPFIELD(RuleLock *, rd_rules);
SWAPFIELD(MemoryContext, rd_rulescxt);
}
+ if (keep_policies)
+ SWAPFIELD(RowSecurityDesc *, rsdesc);
/* toast OID override must be preserved */
SWAPFIELD(Oid, rd_toastoid);
/* pgstat_info must be preserved */
@@ -3345,11 +3430,11 @@ RelationCacheInitializePhase3(void)
/*
* Re-load the row security policies if the relation has them, since
* they are not preserved in the cache. Note that we can never NOT
- * have a policy while relhasrowsecurity is true-
+ * have a policy while relrowsecurity is true,
* RelationBuildRowSecurity will create a single default-deny policy
* if there is no policy defined in pg_rowsecurity.
*/
- if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL)
+ if (relation->rd_rel->relrowsecurity && relation->rsdesc == NULL)
{
RelationBuildRowSecurity(relation);
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 5476a1e7e2..3b101d4483 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -376,10 +376,13 @@ RestoreArchive(Archive *AHX)
/*
* Enable row-security if necessary.
*/
- if (!ropt->enable_row_security)
- ahprintf(AH, "SET row_security = off;\n");
- else
- ahprintf(AH, "SET row_security = on;\n");
+ if (PQserverVersion(AH->connection) >= 90500)
+ {
+ if (!ropt->enable_row_security)
+ ahprintf(AH, "SET row_security = off;\n");
+ else
+ ahprintf(AH, "SET row_security = on;\n");
+ }
/*
* Establish important parameter values right away.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 29153294e2..12811a801a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2777,7 +2777,7 @@ dumpBlobs(Archive *fout, void *arg)
void
getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
{
- PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer query;
PGresult *res;
RowSecurityInfo *rsinfo;
int i_oid;
@@ -2792,6 +2792,8 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
if (fout->remoteVersion < 90500)
return;
+ query = createPQExpBuffer();
+
for (i = 0; i < numTables; i++)
{
TableInfo *tbinfo = &tblinfo[i];
@@ -2809,7 +2811,7 @@ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
* We represent RLS enabled on a table by creating RowSecurityInfo
* object with an empty policy.
*/
- if (tbinfo->hasrowsec)
+ if (tbinfo->rowsec)
{
/*
* Note: use tableoid 0 so that this object won't be mistaken for
@@ -4534,7 +4536,7 @@ getTables(Archive *fout, int *numTables)
int i_relhastriggers;
int i_relhasindex;
int i_relhasrules;
- int i_relhasrowsec;
+ int i_relrowsec;
int i_relhasoids;
int i_relfrozenxid;
int i_relminmxid;
@@ -4588,7 +4590,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "c.relhasrowsecurity, "
+ "c.relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@@ -4629,7 +4631,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@@ -4670,7 +4672,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
@@ -4711,7 +4713,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@@ -4750,7 +4752,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@@ -4788,7 +4790,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@@ -4826,7 +4828,7 @@ getTables(Archive *fout, int *numTables)
"(%s c.relowner) AS rolname, "
"c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
@@ -4864,7 +4866,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4901,7 +4903,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4934,7 +4936,7 @@ getTables(Archive *fout, int *numTables)
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@@ -4962,7 +4964,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@@ -5000,7 +5002,7 @@ getTables(Archive *fout, int *numTables)
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
- "'f'::bool AS relhasrowsecurity, "
+ "'f'::bool AS relrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
@@ -5048,7 +5050,7 @@ getTables(Archive *fout, int *numTables)
i_relhastriggers = PQfnumber(res, "relhastriggers");
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
- i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
+ i_relrowsec = PQfnumber(res, "relrowsecurity");
i_relhasoids = PQfnumber(res, "relhasoids");
i_relfrozenxid = PQfnumber(res, "relfrozenxid");
i_relminmxid = PQfnumber(res, "relminmxid");
@@ -5100,7 +5102,7 @@ getTables(Archive *fout, int *numTables)
tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
- tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
+ tblinfo[i].rowsec = (strcmp(PQgetvalue(res, i, i_relrowsec), "t") == 0);
tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident));
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index b5d820e761..646a2077a6 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -246,7 +246,7 @@ typedef struct _tableInfo
bool hasindex; /* does it have any indexes? */
bool hasrules; /* does it have any rules? */
bool hastriggers; /* does it have any triggers? */
- bool hasrowsec; /* does it have any row-security policy? */
+ bool rowsec; /* does it have any row-security policy? */
bool hasoids; /* does it have OIDs? */
uint32 frozenxid; /* for restore frozen xid */
uint32 minmxid; /* for restore min multi xid */
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 1c1b80f137..21715dc944 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -463,7 +463,7 @@ usage(const char *progname)
printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n"));
printf(_(" -1, --single-transaction restore as a single transaction\n"));
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
- printf(_(" --enable-row-security enable row level security\n"));
+ printf(_(" --enable-row-security enable row level security\n"));
printf(_(" --if-exists use IF EXISTS when dropping objects\n"));
printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n"
" created\n"));
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 97dc2dded2..074be57696 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1204,7 +1204,7 @@ describeOneTableDetails(const char *schemaname,
bool hasindex;
bool hasrules;
bool hastriggers;
- bool hasrowsecurity;
+ bool rowsecurity;
bool hasoids;
Oid tablespace;
char *reloptions;
@@ -1230,7 +1230,7 @@ describeOneTableDetails(const char *schemaname,
{
printfPQExpBuffer(&buf,
"SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, "
- "c.relhastriggers, c.relhasrowsecurity, c.relhasoids, "
+ "c.relhastriggers, c.relrowsecurity, c.relhasoids, "
"%s, c.reltablespace, "
"CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, "
"c.relpersistence, c.relreplident\n"
@@ -1355,7 +1355,7 @@ describeOneTableDetails(const char *schemaname,
tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0;
tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0;
tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0;
- tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
+ tableinfo.rowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0;
tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0;
tableinfo.reloptions = (pset.sversion >= 80200) ?
pg_strdup(PQgetvalue(res, 0, 7)) : NULL;
@@ -1998,18 +1998,17 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
-
+ /* print any row-level policies */
if (pset.sversion >= 90500)
+ {
appendPQExpBuffer(&buf,
",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
gettext_noop("Row-security"));
- if (verbose && pset.sversion >= 90500)
- appendPQExpBuffer(&buf,
- "\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
- /* print any row-level policies */
- if (tableinfo.hasrowsecurity)
- {
+ if (verbose)
+ appendPQExpBuffer(&buf,
+ "\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
+
printfPQExpBuffer(&buf,
"SELECT rs.rsecpolname,\n"
"CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n"
@@ -2019,41 +2018,53 @@ describeOneTableDetails(const char *schemaname,
"FROM pg_catalog.pg_rowsecurity rs\n"
"WHERE rs.rsecrelid = '%s' ORDER BY 1;",
oid);
+
result = PSQLexec(buf.data, false);
if (!result)
goto error_return;
else
tuples = PQntuples(result);
- if (tuples > 0)
- {
+ /*
+ * Handle cases where RLS is enabled and there are policies,
+ * or there aren't policies, or RLS isn't enabled but there
+ * are policies
+ */
+ if (tableinfo.rowsecurity && tuples > 0)
printTableAddFooter(&cont, _("Policies:"));
- for (i = 0; i < tuples; i++)
+
+ if (tableinfo.rowsecurity && tuples == 0)
+ printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)"));
+
+ if (!tableinfo.rowsecurity && tuples > 0)
+ printTableAddFooter(&cont, _("Policies (Row Security Disabled):"));
+
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " POLICY \"%s\"",
+ PQgetvalue(result, i, 0));
+
+ if (!PQgetisnull(result, i, 4))
+ appendPQExpBuffer(&buf, " (%s)",
+ PQgetvalue(result, i, 4));
+
+ if (!PQgetisnull(result, i, 2))
+ appendPQExpBuffer(&buf, " EXPRESSION %s",
+ PQgetvalue(result, i, 2));
+
+ if (!PQgetisnull(result, i, 3))
+ appendPQExpBuffer(&buf, " WITH CHECK %s",
+ PQgetvalue(result, i, 3));
+
+ printTableAddFooter(&cont, buf.data);
+
+ if (!PQgetisnull(result, i, 1))
{
- printfPQExpBuffer(&buf, " POLICY \"%s\"",
- PQgetvalue(result, i, 0));
-
- if (!PQgetisnull(result, i, 4))
- appendPQExpBuffer(&buf, " (%s)",
- PQgetvalue(result, i, 4));
-
- if (!PQgetisnull(result, i, 2))
- appendPQExpBuffer(&buf, " EXPRESSION %s",
- PQgetvalue(result, i, 2));
-
- if (!PQgetisnull(result, i, 3))
- appendPQExpBuffer(&buf, " WITH CHECK %s",
- PQgetvalue(result, i, 3));
+ printfPQExpBuffer(&buf, " APPLIED TO %s",
+ PQgetvalue(result, i, 1));
printTableAddFooter(&cont, buf.data);
-
- if (!PQgetisnull(result, i, 1))
- {
- printfPQExpBuffer(&buf, " APPLIED TO %s",
- PQgetvalue(result, i, 1));
-
- printTableAddFooter(&cont, buf.data);
- }
}
}
PQclear(result);
@@ -2708,6 +2719,10 @@ describeRoles(const char *pattern, bool verbose)
if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
add_role_attribute(&buf, _("Replication"));
+ if (pset.sversion >= 90500)
+ if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
+ add_role_attribute(&buf, _("Bypass RLS"));
+
conns = atoi(PQgetvalue(res, i, 6));
if (conns >= 0)
{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index a4594b6783..886188c036 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1214,11 +1214,12 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "ROLE") == 0))
{
static const char *const list_ALTERUSER[] =
- {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
- "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
- "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
- "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
- "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
+ {"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+ "CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
+ "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+ "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
+ "REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
+ "VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER);
}
@@ -1231,11 +1232,12 @@ psql_completion(const char *text, int start, int end)
{
/* Similar to the above, but don't complete "WITH" again. */
static const char *const list_ALTERUSER_WITH[] =
- {"CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
- "ENCRYPTED", "INHERIT", "LOGIN", "NOCREATEDB", "NOCREATEROLE",
- "NOCREATEUSER", "NOINHERIT", "NOLOGIN", "NOREPLICATION",
- "NOSUPERUSER", "RENAME TO", "REPLICATION", "RESET", "SET",
- "SUPERUSER", "UNENCRYPTED", "VALID UNTIL", NULL};
+ {"BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+ "CREATEUSER", "ENCRYPTED", "INHERIT", "LOGIN", "NOBYPASSRLS",
+ "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+ "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "RENAME TO",
+ "REPLICATION", "RESET", "SET", "SUPERUSER", "UNENCRYPTED",
+ "VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_ALTERUSER_WITH);
}
@@ -2565,10 +2567,10 @@ psql_completion(const char *text, int start, int end)
pg_strcasecmp(prev2_wd, "GROUP") == 0 || pg_strcasecmp(prev2_wd, "USER") == 0))
{
static const char *const list_CREATEROLE[] =
- {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
- "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
- "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
- "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+ {"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+ "CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
+ "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+ "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", "WITH", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE);
@@ -2583,10 +2585,10 @@ psql_completion(const char *text, int start, int end)
{
/* Similar to the above, but don't complete "WITH" again. */
static const char *const list_CREATEROLE_WITH[] =
- {"ADMIN", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE", "CREATEUSER",
- "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOCREATEDB",
- "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT", "NOLOGIN",
- "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
+ {"ADMIN", "BYPASSRLS", "CONNECTION LIMIT", "CREATEDB", "CREATEROLE",
+ "CREATEUSER", "ENCRYPTED", "IN", "INHERIT", "LOGIN", "NOBYPASSRLS",
+ "NOCREATEDB", "NOCREATEROLE", "NOCREATEUSER", "NOINHERIT",
+ "NOLOGIN", "NOREPLICATION", "NOSUPERUSER", "REPLICATION", "ROLE",
"SUPERUSER", "SYSID", "UNENCRYPTED", "VALID UNTIL", NULL};
COMPLETE_WITH_LIST(list_CREATEROLE_WITH);
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index af0475e831..bee67ddb37 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201409191
+#define CATALOG_VERSION_NO 201409241
#endif
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f6353514ca..22c55a9490 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,7 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
bool relhasrules; /* has (or has had) any rules */
bool relhastriggers; /* has (or has had) any TRIGGERs */
bool relhassubclass; /* has (or has had) derived classes */
- bool relhasrowsecurity; /* has (or has had) row-security policy */
+ bool relrowsecurity; /* row-security is enabled or not */
bool relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -119,7 +119,7 @@ typedef FormData_pg_class *Form_pg_class;
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
-#define Anum_pg_class_relhasrowsecurity 24
+#define Anum_pg_class_relrowsecurity 24
#define Anum_pg_class_relispopulated 25
#define Anum_pg_class_relreplident 26
#define Anum_pg_class_relfrozenxid 27
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
index 95d8a6d117..fcc991173b 100644
--- a/src/include/commands/policy.h
+++ b/src/include/commands/policy.h
@@ -16,6 +16,7 @@
#define POLICY_H
#include "nodes/parsenodes.h"
+#include "utils/relcache.h"
extern void RelationBuildRowSecurity(Relation relation);
@@ -24,10 +25,10 @@ extern void RemovePolicyById(Oid policy_id);
extern Oid CreatePolicy(CreatePolicyStmt *stmt);
extern Oid AlterPolicy(AlterPolicyStmt *stmt);
-Oid get_relation_policy_oid(Oid relid,
- const char *policy_name, bool missing_ok);
+extern Oid get_relation_policy_oid(Oid relid, const char *policy_name,
+ bool missing_ok);
-Oid rename_policy(RenameStmt *stmt);
+extern Oid rename_policy(RenameStmt *stmt);
#endif /* POLICY_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 889bcd201f..c53e7851cc 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2046,7 +2046,7 @@ pg_tables| SELECT n.nspname AS schemaname,
c.relhasindex AS hasindexes,
c.relhasrules AS hasrules,
c.relhastriggers AS hastriggers,
- c.relhasrowsecurity AS hasrowsecurity
+ c.relrowsecurity AS rowsecurity
FROM ((pg_class c
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))