</row>
<row>
- <entry><structfield>relhasrowsecurity</structfield></entry>
+ <entry><structfield>relrowsecurity</structfield></entry>
<entry><type>bool</type></entry>
+ <entry></entry>
<entry>
True if table has row-security enabled; see
<link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
<note>
<para>
- <literal>pg_class.relhasrowsecurity</literal>
+ <literal>pg_class.relrowsecurity</literal>
True if the table has row-security enabled.
Must be true if the table has a row-security policy in this catalog.
</para>
<entry>True if table has (or once had) triggers</entry>
</row>
<row>
- <entry><structfield>hasrowsecurity</structfield></entry>
+ <entry><structfield>rowsecurity</structfield></entry>
<entry><type>boolean</type></entry>
- <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasrowsecurity</literal></entry>
- <entry>True if table has row security enabled</entry>
+ <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relrowsecurity</literal></entry>
+ <entry>True if row security is enabled on the table</entry>
</row>
</tbody>
</tgroup>
<para>
The allowed values of <varname>row_security</> are
- <literal>on</> (apply normally- not to superuser or table owner),
+ <literal>on</> (apply normally - not to superuser or table owner),
<literal>off</> (fail if row security would be applied), and
- <literal>force</> (apply always- even to superuser and table owner).
+ <literal>force</> (apply always - even to superuser and table owner).
</para>
<para>
</para>
</sect1>
+ <sect1 id="ddl-rowsecurity">
+ <title>Row Security Policies</title>
+
+ <indexterm zone="ddl-rowsecurity">
+ <primary>rowsecurity</primary>
+ </indexterm>
+
+ <indexterm zone="ddl-rowsecurity">
+ <primary>rls</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>policies</primary>
+ <see>policy</see>
+ </indexterm>
+
+ <indexterm zone="ddl-rowsecurity">
+ <primary>POLICY</primary>
+ </indexterm>
+
+ <para>
+ In addition to the <xref linkend="ddl-priv"> system available through
+ <xref linkend="sql-grant">, 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 <xref linkend="ddl-priv"> system. This is
+ also known to as Row Level Security.
+ </para>
+
+ <para>
+ When row security is enabled on a table with
+ <xref linkend="sql-altertable">, 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 <literal>TRUNCATE</>, and
+ <literal>REFERENCES</> are not subject to row security.
+ </para>
+
+ <para>
+ Row security policies can be specific to commands, or to roles, or to
+ both. The commands available are <literal>SELECT</>, <literal>INSERT</>,
+ <literal>UPDATE</>, and <literal>DELETE</>. Multiple roles can be
+ assigned to a given policy and normal role membership and inheiritance
+ rules apply.
+ </para>
+
+ <para>
+ 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 <literal>leakproof</literal> 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.
+ </para>
+
+ <para>
+ Enabling and disabling row security, as well as adding policies to a
+ table, is always the privilege of the owner only.
+ </para>
+
+ <para>
+ Policies are created using the <xref linkend="sql-createpolicy">
+ command, altered using the <xref linkend="sql-alterpolicy"> command,
+ and dropped using the <xref linkend="sql-droppolicy"> command. To
+ enable and disable row security for a given table, use the
+ <xref linkend="sql-altertable"> command.
+ </para>
+
+ <para>
+ 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
+ <xref linkend="guc-row-security"> to <literal>force</literal>. Any
+ user can request that row security be bypassed by setting
+ <xref linkend="guc-row-security"> to <literal>off</literal>. 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 <literal>BYPASSRLS</literal> role attribute. This
+ attribute can only be set by a superuser.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ When multiple policies apply to a given query, they are combined using
+ <literal>OR</literal>, similar to how a given role has the privileges
+ of all roles which they are a member of.
+ </para>
+
+ <para>
+ 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.
+ </para>
+
+ <para>
+ To enable row security for a table,
+ the <command>ALTER TABLE</command> is used. For example, to enable
+ row level security for the table accounts, use:
+ </para>
+
+<programlisting>
+-- Create the table first
+CREATE TABLE accounts (manager text, company text, contact_email text);
+ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;
+</programlisting>
+
+ <para>
+ To create a policy on the account relation to allow the managers role
+ to view the rows of their accounts, the <command>CREATE POLICY</command>
+ command can be used:
+ </para>
+
+<programlisting>
+CREATE POLICY account_managers ON accounts TO managers
+ USING (manager = current_user);
+</programlisting>
+
+ <para>
+ If no role is specified, or the special <quote>user</quote> name
+ <literal>PUBLIC</literal> 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:
+ </para>
+
+<programlisting>
+CREATE POLICY user_policy ON users
+ USING (user = current_user);
+</programlisting>
+
+ <para>
+ 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:
+ </para>
+
+<programlisting>
+CREATE POLICY user_policy ON users
+ USING (true)
+ WITH CHECK (user = current_user);
+</programlisting>
+
+ <para>
+ Row security can be disabled with the <command>ALTER TABLE</command>
+ 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.
+ </para>
+
+ </sect1>
+
<sect1 id="ddl-schemas">
<title>Schemas</title>
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
<xref linkend="SQL-CREATEPOLICY">.
</varlistentry>
<varlistentry id="SQL-CREATEPOLICY-UPDATE">
- <term><literal>DELETE</></term>
+ <term><literal>UPDATE</></term>
<listitem>
<para>
Using <literal>UPDATE</literal> for a policy means that it will apply
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--enable-row-security</></term>
+ <listitem>
+ <para>
+ This option is relevant only when dumping the contents of a table
+ which has row security. By default, pg_dump will set
+ <literal>ROW_SECURITY</literal> to <literal>OFF</literal>, 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 <application>pg_dump</application> to set
+ row_security to 'ON' instead, allowing the user to dump the contents
+ of the table which they have access to.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--exclude-table-data=<replaceable class="parameter">table</replaceable></option></term>
<listitem>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--enable-row-security</></term> <listitem>
+ <para>
+ This option is relevant only when restoring the contents of a table
+ which has row security. By default, pg_restore will set
+ <literal>ROW_SECURITY</literal> to <literal>OFF</literal>, 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 <application>pg_restore</application> 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.
+ </para>
+
+ <para>
+ Note that this option currently also requires the dump be in INSERT
+ format as COPY TO does not support row security.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--if-exists</option></term>
<listitem>
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);
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';
char cmd;
if (!cmd_name)
- elog(ERROR, "Unregonized command.");
+ elog(ERROR, "unregonized command");
if (strcmp(cmd_name, "all") == 0)
cmd = 0;
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;
}
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
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 */
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))));
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],
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",
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 */
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 */
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
* 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).
*
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. */
{
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:
{
HeapTuple tuple;
Form_pg_class classform;
- bool relhasrowsecurity;
+ bool relrowsecurity;
Oid user_id = checkAsUser ? checkAsUser : GetUserId();
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
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;
/*
* 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;
}
* 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;
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
else
relation->trigdesc = NULL;
- if (relation->rd_rel->relhasrowsecurity)
+ if (relation->rd_rel->relrowsecurity)
RelationBuildRowSecurity(relation);
else
relation->rsdesc = NULL;
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);
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
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 */
/*
* 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);
/*
* 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.
void
getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
{
- PQExpBuffer query = createPQExpBuffer();
+ PQExpBuffer query;
PGresult *res;
RowSecurityInfo *rsinfo;
int i_oid;
if (fout->remoteVersion < 90500)
return;
+ query = createPQExpBuffer();
+
for (i = 0; i < numTables; i++)
{
TableInfo *tbinfo = &tblinfo[i];
* 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
int i_relhastriggers;
int i_relhasindex;
int i_relhasrules;
- int i_relhasrowsec;
+ int i_relrowsec;
int i_relhasoids;
int i_relfrozenxid;
int i_relminmxid;
"(%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, "
"(%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, "
"(%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, "
"(%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, "
"(%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, "
"(%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, "
"(%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, "
"(%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,"
"(%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,"
"(%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,"
"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,"
"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,"
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");
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));
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 */
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"));
bool hasindex;
bool hasrules;
bool hastriggers;
- bool hasrowsecurity;
+ bool rowsecurity;
bool hasoids;
Oid tablespace;
char *reloptions;
{
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"
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;
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"
"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++)
- {
- printfPQExpBuffer(&buf, " POLICY \"%s\"",
- PQgetvalue(result, i, 0));
- if (!PQgetisnull(result, i, 4))
- appendPQExpBuffer(&buf, " (%s)",
- PQgetvalue(result, i, 4));
+ if (tableinfo.rowsecurity && tuples == 0)
+ printTableAddFooter(&cont, _("Policies (Row Security Enabled): (None)"));
- if (!PQgetisnull(result, i, 2))
- appendPQExpBuffer(&buf, " EXPRESSION %s",
- PQgetvalue(result, i, 2));
+ if (!tableinfo.rowsecurity && tuples > 0)
+ printTableAddFooter(&cont, _("Policies (Row Security Disabled):"));
- if (!PQgetisnull(result, i, 3))
- appendPQExpBuffer(&buf, " WITH CHECK %s",
- PQgetvalue(result, i, 3));
+ /* Might be an empty set - that's ok */
+ for (i = 0; i < tuples; i++)
+ {
+ printfPQExpBuffer(&buf, " POLICY \"%s\"",
+ PQgetvalue(result, i, 0));
- printTableAddFooter(&cont, buf.data);
+ if (!PQgetisnull(result, i, 4))
+ appendPQExpBuffer(&buf, " (%s)",
+ PQgetvalue(result, i, 4));
- if (!PQgetisnull(result, i, 1))
- {
- printfPQExpBuffer(&buf, " APPLIED TO %s",
- PQgetvalue(result, i, 1));
+ if (!PQgetisnull(result, i, 2))
+ appendPQExpBuffer(&buf, " EXPRESSION %s",
+ PQgetvalue(result, i, 2));
- printTableAddFooter(&cont, buf.data);
- }
+ 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, " APPLIED TO %s",
+ PQgetvalue(result, i, 1));
+
+ printTableAddFooter(&cont, buf.data);
}
}
PQclear(result);
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)
{
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);
}
{
/* 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);
}
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);
{
/* 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);
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201409191
+#define CATALOG_VERSION_NO 201409241
#endif
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 */
#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
#define POLICY_H
#include "nodes/parsenodes.h"
+#include "utils/relcache.h"
extern void RelationBuildRowSecurity(Relation relation);
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 */
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)))