From 6550b901fe7c47c03775400e0c790c6c1234a017 Mon Sep 17 00:00:00 2001 From: Stephen Frost Date: Wed, 24 Sep 2014 16:32:22 -0400 Subject: [PATCH] Code review for row security. Buildfarm member tick identified an issue where the policies in the relcache for a relation were were being replaced underneath a running query, leading to segfaults while processing the policies to be added to a query. Similar to how TupleDesc RuleLocks are handled, add in a equalRSDesc() function to check if the policies have actually changed and, if not, swap back the rsdesc field (using the original instead of the temporairly built one; the whole structure is swapped and then specific fields swapped back). This now passes a CLOBBER_CACHE_ALWAYS for me and should resolve the buildfarm error. In addition to addressing this, add a new chapter in Data Definition under Privileges which explains row security and provides examples of its usage, change \d to always list policies (even if row security is disabled- but note that it is disabled, or enabled with no policies), rework check_role_for_policy (it really didn't need the entire policy, but it did need to be using has_privs_of_role()), and change the field in pg_class to relrowsecurity from relhasrowsecurity, based on Heikki's suggestion. Also from Heikki, only issue SET ROW_SECURITY in pg_restore when talking to a 9.5+ server, list Bypass RLS in \du, and document --enable-row-security options for pg_dump and pg_restore. Lastly, fix a number of minor whitespace and typo issues from Heikki, Dimitri, add a missing #include, per Peter E, fix a few minor variable-assigned-but-not-used and resource leak issues from Coverity and add tab completion for role attribute bypassrls as well. --- doc/src/sgml/catalogs.sgml | 11 +- doc/src/sgml/config.sgml | 4 +- doc/src/sgml/ddl.sgml | 168 +++++++++++++++++++++++++++ doc/src/sgml/ref/alter_table.sgml | 2 +- doc/src/sgml/ref/create_policy.sgml | 2 +- doc/src/sgml/ref/pg_dump.sgml | 17 +++ doc/src/sgml/ref/pg_restore.sgml | 23 ++++ src/backend/catalog/heap.c | 2 +- src/backend/catalog/system_views.sql | 2 +- src/backend/commands/policy.c | 17 ++- src/backend/commands/tablecmds.c | 4 +- src/backend/rewrite/rowsecurity.c | 31 ++--- src/backend/utils/adt/ri_triggers.c | 4 +- src/backend/utils/cache/relcache.c | 91 ++++++++++++++- src/bin/pg_dump/pg_backup_archiver.c | 11 +- src/bin/pg_dump/pg_dump.c | 36 +++--- src/bin/pg_dump/pg_dump.h | 2 +- src/bin/pg_dump/pg_restore.c | 2 +- src/bin/psql/describe.c | 85 ++++++++------ src/bin/psql/tab-complete.c | 38 +++--- src/include/catalog/catversion.h | 2 +- src/include/catalog/pg_class.h | 4 +- src/include/commands/policy.h | 7 +- src/test/regress/expected/rules.out | 2 +- 24 files changed, 442 insertions(+), 125 deletions(-) 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 + relrowsecurity bool + 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 + rowsecurity boolean - 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)))