diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index 68f8434..34aa438
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 239,244 ****
--- 239,249 ----
+ pg_rowsecuritylevelsec
+ row-level security policy of relation
+
+
+ pg_seclabelsecurity labels on database objects
***************
*** 1936,1941 ****
--- 1941,1955 ----
+ relhasrowsecurity
+ bool
+
+ True if table has (or once had) row-security policy; see
+ pg_rowsecurity catalog
+
+
+
+ relhassubclassbool
***************
*** 5328,5333 ****
--- 5342,5413 ----
+
+ pg_rowsecurity
+
+
+ pg_rowsecurity
+
+
+
+ The catalog pg_rowsecurity stores expression
+ tree of row-security policy to be performed on a particular relation.
+
+
+
+
+ pg_rowsecurity Columns
+
+
+
+
+ Name
+ Type
+ References
+ Description
+
+
+
+
+
+ rsecpolname
+ NameData
+
+ The name of the row-security policy
+
+
+
+ rsecrelid
+ oid
+ pg_class.oid
+ The table to which the row-security policy is applied
+
+
+
+ rseccmd
+ char
+
+ The command to which the row-security policy is applied.
+
+
+
+ rsecqual
+ pg_node_tree
+
+ An expression tree that describes what is to be performed by the row-security policy
+
+
+
+
+
+
+
+ pg_class.relhasrowsecurity
+ must be true if a table has row-security policy in this catalog.
+
+
+
+ pg_seclabel
*************** SELECT * FROM pg_locks pl LEFT JOIN pg_p
*** 9133,9138 ****
--- 9213,9224 ----
pg_class.relhastriggersTrue if table has (or once had) triggers
+
+ hasrowsecurity
+ boolean
+ pg_class.relhasrowsecurity
+ True if table has (or once had) row security policies
+
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
new file mode 100644
index b685e16..7aa3128
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
*************** Complete list of usable sgml source file
*** 25,30 ****
--- 25,31 ----
+
*************** Complete list of usable sgml source file
*** 69,74 ****
--- 70,76 ----
+
*************** Complete list of usable sgml source file
*** 110,115 ****
--- 112,118 ----
+
diff --git a/doc/src/sgml/ref/alter_policy.sgml b/doc/src/sgml/ref/alter_policy.sgml
new file mode 100644
index ...04198d3
*** a/doc/src/sgml/ref/alter_policy.sgml
--- b/doc/src/sgml/ref/alter_policy.sgml
***************
*** 0 ****
--- 1,110 ----
+
+
+
+
+ ALTER POLICY
+
+
+
+ ALTER POLICY
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER POLICY
+ change the condition of a row-security policy
+
+
+
+
+ ALTER POLICY policy name ON table_name
+ FOR command
+ USING ( condition )
+
+ where command can be one of:
+
+ ALL | SELECT | INSERT | UPDATE | DELETE
+
+
+
+
+ Description
+
+
+ ALTER POLICY changes the
+ condition of an existing row-security policy.
+
+
+
+ To use ALTER POLICY, you must own the table that
+ the policy applies to.
+
+
+
+
+ Parameters
+
+
+
+ policy_name
+
+
+ The name of an existing policy to alter.
+
+
+
+
+
+ table_name
+
+
+ The name (optionally schema-qualified) of the table that the
+ policy applies to.
+
+
+
+
+
+ command
+
+
+ The command that the policy applies to.
+
+
+
+
+
+ condition
+
+
+ The new condition for the policy.
+
+
+
+
+
+
+
+ Compatibility
+
+
+ ALTER POLICY is a
+ PostgreSQL language extension, as is the
+ entire query rewrite system.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/alter_role.sgml b/doc/src/sgml/ref/alter_role.sgml
new file mode 100644
index bcd46d5..0471daa
*** a/doc/src/sgml/ref/alter_role.sgml
--- b/doc/src/sgml/ref/alter_role.sgml
*************** ALTER ROLE connlimit
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'password'
| VALID UNTIL 'timestamp'
*************** ALTER ROLE { connlimitPASSWORD> passwordENCRYPTED>
diff --git a/doc/src/sgml/ref/create_policy.sgml b/doc/src/sgml/ref/create_policy.sgml
new file mode 100644
index ...f1e030d
*** a/doc/src/sgml/ref/create_policy.sgml
--- b/doc/src/sgml/ref/create_policy.sgml
***************
*** 0 ****
--- 1,124 ----
+
+
+
+
+ CREATE POLICY
+
+
+
+ CREATE POLICY
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE POLICY
+ define a new row-security policy
+
+
+
+
+ CREATE POLICY policy_name ON table_name
+ FOR command
+ USING ( condition )
+
+ where command can be one of:
+
+ ALL | SELECT | INSERT | UPDATE | DELETE
+
+
+
+
+ Description
+
+
+ CREATE POLICY defines a new row-security policy applying to a specified table for a specific command.
+
+
+
+ The PostgreSQL row-security system allows
+ one to define per command row level security policies on a table. A policy
+ consists of a name, a table, a command and a qualifier expression.
+
+
+
+
+
+ Parameters
+
+
+
+ policy_name
+
+
+ The name of a policy to create. This must be distinct from the
+ name of any other policy for the same table and command.
+
+
+
+
+
+ table_name
+
+
+ The name (optionally schema-qualified) of the table the
+ policy applies to.
+
+
+
+
+
+ command
+
+
+ The command to which the policy applies. Valid commands are
+ ALL, SELECT,
+ INSERT, UPDATE,
+ or DELETE
+
+
+
+
+
+ condition
+
+
+ Any SQL conditional expression (returning
+ boolean). The condition expression cannot contain
+ any aggregate or window functions.
+
+
+
+
+
+
+
+ Notes
+
+
+ You must be the owner of a table to create or change policies for it.
+
+
+
+ Compatibility
+
+
+ CREATE POLICY is a
+ PostgreSQL language extension, as is the
+ entire query rewrite system.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
new file mode 100644
index 641e350..d15c613
*** a/doc/src/sgml/ref/create_role.sgml
--- b/doc/src/sgml/ref/create_role.sgml
*************** CREATE ROLE connlimit
| [ ENCRYPTED | UNENCRYPTED ] PASSWORD 'password'
| VALID UNTIL 'timestamp'
*************** CREATE ROLE connlimit
diff --git a/doc/src/sgml/ref/drop_policy.sgml b/doc/src/sgml/ref/drop_policy.sgml
new file mode 100644
index ...fbcad60
*** a/doc/src/sgml/ref/drop_policy.sgml
--- b/doc/src/sgml/ref/drop_policy.sgml
***************
*** 0 ****
--- 1,118 ----
+
+
+
+
+ DROP POLICY
+
+
+
+ DROP POLICY
+ 7
+ SQL - Language Statements
+
+
+
+ DROP POLICY
+ remove a row-security policy
+
+
+
+
+ DROP POLICY [ IF EXISTS ] policy_name ON table_name
+ FOR command
+
+ where command can be one of:
+
+ ALL | SELECT | INSERT | UPDATE | DELETE
+
+
+
+
+ Description
+
+
+ DROP POLICY drops a row-security policy.
+
+
+
+
+ Parameters
+
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if the policy does not exist. A notice is issued
+ in this case.
+
+
+
+ policy_name
+
+
+ The name of the policy to drop.
+
+
+
+
+
+ table_name
+
+
+ The name (optionally schema-qualified) of the table that
+ the policy applies to.
+
+
+
+
+
+ command
+
+
+ The command that the policy applies to.
+
+
+
+
+
+
+
+ Examples
+
+
+ To drop the row-security policy called p1 on the
+ table named my_table for the ALL command:
+
+
+ DROP POLICY p1 ON my_table FOR ALL;
+
+
+
+
+
+ Compatibility
+
+
+ DROP POLICY is a
+ PostgreSQL language extension, as is the
+ entire query rewrite system.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
new file mode 100644
index 6ec1263..10c9a6d
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 53,58 ****
--- 53,59 ----
&alterOperator;
&alterOperatorClass;
&alterOperatorFamily;
+ &alterPolicy;
&alterRole;
&alterRule;
&alterSchema;
***************
*** 97,102 ****
--- 98,104 ----
&createOperator;
&createOperatorClass;
&createOperatorFamily;
+ &createPolicy;
&createRole;
&createRule;
&createSchema;
***************
*** 138,143 ****
--- 140,146 ----
&dropOperatorClass;
&dropOperatorFamily;
&dropOwned;
+ &dropPolicy;
&dropRole;
&dropRule;
&dropSchema;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index a974bd5..b257b02
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** POSTGRES_BKI_SRCS = $(addprefix $(top_sr
*** 39,45 ****
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
! pg_foreign_table.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
toasting.h indexing.h \
)
--- 39,45 ----
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
! pg_foreign_table.h pg_rowsecurity.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
new file mode 100644
index d9745ca..d30612c
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
*************** has_createrole_privilege(Oid roleid)
*** 5080,5085 ****
--- 5080,5104 ----
return result;
}
+ bool
+ has_bypassrls_privilege(Oid roleid)
+ {
+ bool result = false;
+ HeapTuple utup;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ if (HeapTupleIsValid(utup))
+ {
+ result = ((Form_pg_authid) GETSTRUCT(utup))->rolbypassrls;
+ ReleaseSysCache(utup);
+ }
+ return result;
+ }
+
/*
* Fetch pg_default_acl entry for given role, namespace and object type
* (object type must be given in pg_default_acl's encoding).
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index d41ba49..256486c
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 45,50 ****
--- 45,51 ----
#include "catalog/pg_opfamily.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
+ #include "catalog/pg_rowsecurity.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
***************
*** 57,62 ****
--- 58,64 ----
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
+ #include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
*************** doDeletion(const ObjectAddress *object,
*** 1249,1254 ****
--- 1251,1260 ----
RemoveEventTriggerById(object->objectId);
break;
+ case OCLASS_ROWSECURITY:
+ RemovePolicyById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2316,2321 ****
--- 2322,2330 ----
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
+
+ case RowSecurityRelationId:
+ return OCLASS_ROWSECURITY;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
new file mode 100644
index 33eef9f..eb64b3d
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** InsertPgClassTuple(Relation pg_class_des
*** 799,804 ****
--- 799,805 ----
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_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/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index d143a44..b16293c
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** getObjectDescription(const ObjectAddress
*** 2166,2171 ****
--- 2166,2227 ----
break;
}
+ case OCLASS_ROWSECURITY:
+ {
+ Relation rsec_rel;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ Form_pg_rowsecurity form_rsec;
+
+ rsec_rel = heap_open(RowSecurityRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ sscan = systable_beginscan(rsec_rel, RowSecurityOidIndexId,
+ true, NULL, 1, &skey);
+
+ tuple = systable_getnext(sscan);
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for row-security relation %u",
+ object->objectId);
+
+ form_rsec = (Form_pg_rowsecurity) GETSTRUCT(tuple);
+
+ appendStringInfo(&buffer, _("row-security of "));
+ getRelationDescription(&buffer, form_rsec->rsecrelid);
+
+ switch(form_rsec->rseccmd)
+ {
+ case ROWSECURITY_CMD_ALL:
+ appendStringInfo(&buffer, _(" FOR ALL"));
+ break;
+ case ROWSECURITY_CMD_SELECT:
+ appendStringInfo(&buffer, _(" FOR SELECT"));
+ break;
+ case ROWSECURITY_CMD_INSERT:
+ appendStringInfo(&buffer, _(" FOR INSERT"));
+ break;
+ case ROWSECURITY_CMD_UPDATE:
+ appendStringInfo(&buffer, _(" FOR UPDATE"));
+ break;
+ case ROWSECURITY_CMD_DELETE:
+ appendStringInfo(&buffer, _(" FOR DELETE"));
+ break;
+ default:
+ elog(ERROR, "unrecognized row-security command type: %c",
+ form_rsec->rseccmd);
+ }
+
+ systable_endscan(sscan);
+ heap_close(rsec_rel, AccessShareLock);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 1bde175..fb7284d
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_roles AS
*** 19,24 ****
--- 19,25 ----
rolconnlimit,
'********'::text as rolpassword,
rolvaliduntil,
+ rolbypassrls,
setconfig as rolconfig,
pg_authid.oid
FROM pg_authid LEFT JOIN pg_db_role_setting s
*************** CREATE VIEW pg_tables AS
*** 89,95 ****
T.spcname AS tablespace,
C.relhasindex AS hasindexes,
C.relhasrules AS hasrules,
! C.relhastriggers AS hastriggers
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';
--- 90,97 ----
T.spcname AS tablespace,
C.relhasindex AS hasindexes,
C.relhasrules AS hasrules,
! C.relhastriggers AS hastriggers,
! c.relhasrowsecurity AS hasrowsecurity
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/Makefile b/src/backend/commands/Makefile
new file mode 100644
index 22f116b..b1ac704
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
*************** OBJS = aggregatecmds.o alter.o analyze.o
*** 17,23 ****
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
! portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
--- 17,23 ----
dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
! policy.o portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index fbd7492..e609f1d
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 37,43 ****
--- 37,45 ----
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
#include "parser/parse_relation.h"
+ #include "nodes/makefuncs.h"
#include "rewrite/rewriteHandler.h"
+ #include "rewrite/rowsecurity.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
*************** DoCopy(const CopyStmt *stmt, const char
*** 784,789 ****
--- 786,792 ----
bool pipe = (stmt->filename == NULL);
Relation rel;
Oid relid;
+ Node *query = NULL;
/* Disallow COPY to/from file or program except to superusers. */
if (!pipe && !superuser())
*************** DoCopy(const CopyStmt *stmt, const char
*** 816,847 ****
rel = heap_openrv(stmt->relation,
(is_from ? RowExclusiveLock : AccessShareLock));
! relid = RelationGetRelid(rel);
! rte = makeNode(RangeTblEntry);
! rte->rtekind = RTE_RELATION;
! rte->relid = RelationGetRelid(rel);
! rte->relkind = rel->rd_rel->relkind;
! rte->requiredPerms = required_access;
! tupDesc = RelationGetDescr(rel);
! attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
! foreach(cur, attnums)
{
! int attno = lfirst_int(cur) -
! FirstLowInvalidHeapAttributeNumber;
! if (is_from)
! rte->modifiedCols = bms_add_member(rte->modifiedCols, attno);
! else
! rte->selectedCols = bms_add_member(rte->selectedCols, attno);
}
- ExecCheckRTPerms(list_make1(rte), true);
}
else
{
Assert(stmt->query);
relid = InvalidOid;
rel = NULL;
}
--- 819,892 ----
rel = heap_openrv(stmt->relation,
(is_from ? RowExclusiveLock : AccessShareLock));
! /*
! * If the relation has a row security policy, then perform a "query"
! * copy. This will allow for the policies to be applied appropriately
! * to the relation.
! */
! if (rel->rd_rel->relhasrowsecurity)
! {
! SelectStmt *select;
! ColumnRef *cr;
! ResTarget *target;
! RangeVar *from;
! /* Build target list */
! cr = makeNode(ColumnRef);
! cr->fields = list_make1(makeNode(A_Star));
! cr->location = 1;
! target = makeNode(ResTarget);
! target->name = NULL;
! target->indirection = NIL;
! target->val = (Node *) cr;
! target->location = 1;
!
! /* Build FROM clause */
! from = makeRangeVar(NULL, RelationGetRelationName(rel), 1);
!
! /* Build query */
! select = makeNode(SelectStmt);
! select->targetList = list_make1(target);
! select->fromClause = list_make1(from);
!
! query = (Node*) select;
!
! relid = InvalidOid;
!
! /* Close the handle to the relation as it is no longer needed. */
! heap_close(rel, (is_from ? RowExclusiveLock : AccessShareLock));
! rel = NULL;
! }
! else
{
! relid = RelationGetRelid(rel);
! rte = makeNode(RangeTblEntry);
! rte->rtekind = RTE_RELATION;
! rte->relid = RelationGetRelid(rel);
! rte->relkind = rel->rd_rel->relkind;
! rte->requiredPerms = required_access;
! tupDesc = RelationGetDescr(rel);
! attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
! foreach(cur, attnums)
! {
! int attno = lfirst_int(cur) -
! FirstLowInvalidHeapAttributeNumber;
!
! if (is_from)
! rte->modifiedCols = bms_add_member(rte->modifiedCols, attno);
! else
! rte->selectedCols = bms_add_member(rte->selectedCols, attno);
! }
! ExecCheckRTPerms(list_make1(rte), true);
}
}
else
{
Assert(stmt->query);
+ query = stmt->query;
relid = InvalidOid;
rel = NULL;
}
*************** DoCopy(const CopyStmt *stmt, const char
*** 861,867 ****
}
else
{
! cstate = BeginCopyTo(rel, stmt->query, queryString,
stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
*processed = DoCopyTo(cstate); /* copy from database to file */
--- 906,912 ----
}
else
{
! cstate = BeginCopyTo(rel, query, queryString,
stmt->filename, stmt->is_program,
stmt->attlist, stmt->options);
*processed = DoCopyTo(cstate); /* copy from database to file */
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 754264e..2584ef9
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 995,1000 ****
--- 995,1001 ----
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
+ case OCLASS_ROWSECURITY:
return true;
case MAX_OCLASS:
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
new file mode 100644
index ...9c149d1
*** a/src/backend/commands/policy.c
--- b/src/backend/commands/policy.c
***************
*** 0 ****
--- 1,723 ----
+ /*-------------------------------------------------------------------------
+ *
+ * policy.c
+ * Commands for manipulating policies.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/commands/policy.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/genam.h"
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/catalog.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/namespace.h"
+ #include "catalog/objectaddress.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "commands/policy.h"
+ #include "miscadmin.h"
+ #include "nodes/nodeFuncs.h"
+ #include "nodes/pg_list.h"
+ #include "optimizer/clauses.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "parser/parse_relation.h"
+ #include "storage/lock.h"
+ #include "utils/acl.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/inval.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+
+ static void RangeVarCallbackForCreatePolicy(const RangeVar *rv,
+ Oid relid, Oid oldrelid, void *arg);
+ static const char parse_row_security_command(const char *cmd_name);
+ static RowSecurityEntry* create_row_security_entry(Oid id, Expr *qual,
+ MemoryContext context);
+
+ /*
+ * Callback to RangeVarGetRelidExtended().
+ *
+ * Checks the following:
+ * - the relation specified is a table.
+ * - current user owns the table.
+ * - the table is not a system table.
+ *
+ * If any of these checks fails then an error is raised.
+ */
+ static void
+ RangeVarCallbackForCreatePolicy(const RangeVar *rv, Oid relid, Oid oldrelid,
+ void *arg)
+ {
+ HeapTuple tuple;
+ Form_pg_class classform;
+ char relkind;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tuple))
+ return;
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ relkind = classform->relkind;
+
+ /* Must own relation. */
+ if (!pg_class_ownercheck(relid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
+
+ /* No system table modifications unless explicitly allowed. */
+ if (!allowSystemTableMods && IsSystemClass(relid, classform))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied: \"%s\" is a system catalog",
+ rv->relname)));
+
+ /* Relation type MUST be a table. */
+ if (relkind != RELKIND_RELATION)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a table", rv->relname)));
+
+ ReleaseSysCache(tuple);
+ }
+
+ /*
+ * parse_row_security_command -
+ * helper function to convert full commands strings to their char
+ * representation.
+ *
+ * cmd_name - full string command name. Valid values are 'all', 'select',
+ * 'insert', 'update' and 'delete'.
+ *
+ */
+ static const char
+ parse_row_security_command(const char *cmd_name)
+ {
+ char cmd;
+
+ if (strcmp(cmd_name, "all") == 0)
+ cmd = ROWSECURITY_CMD_ALL;
+ else if (strcmp(cmd_name, "select") == 0)
+ cmd = ROWSECURITY_CMD_SELECT;
+ else if (strcmp(cmd_name, "insert") == 0)
+ cmd = ROWSECURITY_CMD_INSERT;
+ else if (strcmp(cmd_name, "update") == 0)
+ cmd = ROWSECURITY_CMD_UPDATE;
+ else if (strcmp(cmd_name, "delete") == 0)
+ cmd = ROWSECURITY_CMD_DELETE;
+ else
+ elog(ERROR, "Unregonized command.");
+ /* error unrecognized command */
+
+ return cmd;
+ }
+
+ /*
+ * create_row_security_entry -
+ * helper function to create a RowSecurityEntry.
+ *
+ * id - the oid of the row-security policy.
+ * qual - the qualifier expression of the row-security policy.
+ * context - the memory context to which the entry belongs.
+ */
+ static RowSecurityEntry *
+ create_row_security_entry(Oid id, Expr *qual, MemoryContext context)
+ {
+ RowSecurityEntry *entry;
+
+ entry = MemoryContextAllocZero(context, sizeof(RowSecurityEntry));
+ entry->rsecid = id;
+ entry->qual = copyObject(qual);
+ entry->hassublinks = contain_subplans((Node *) entry->qual);
+
+ return entry;
+ }
+
+ /*
+ * Load row-security policy from the catalog, and keep it in
+ * the relation cache.
+ */
+ void
+ RelationBuildRowSecurity(Relation relation)
+ {
+ Relation catalog;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+ MemoryContext rscxt = NULL;
+ RowSecurityDesc *rsdesc = NULL;
+
+ catalog = heap_open(RowSecurityRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey,
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+
+ sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+ NULL, 1, &skey);
+ PG_TRY();
+ {
+ /*
+ * Loop through the row-level security entries for this relation, if
+ * any. While we currently only support one command type for row-level
+ * security, eventually we will support multiple types and we will
+ * need to find the correct one (or possibly merge them?).
+ */
+ while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ {
+ Datum value_datum;
+ char cmd_value;
+ char *qual_value;
+ Expr *qual_expr;
+ char *policy_name_value;
+ Oid policy_id;
+ ListCell *item;
+ bool isnull;
+ RowSecurityPolicy *policy = NULL;
+ RowSecurityPolicy *temp_policy;
+
+ /*
+ * Set up the memory context inside our loop to ensure we are only
+ * building it when we actually need it.
+ */
+ if (!rsdesc)
+ {
+ rscxt = AllocSetContextCreate(CacheMemoryContext,
+ "Row-security descriptor",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+ rsdesc = MemoryContextAllocZero(rscxt, sizeof(RowSecurityDesc));
+ rsdesc->rscxt = rscxt;
+ }
+
+ /* Get policy command */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ RelationGetDescr(catalog), &isnull);
+ Assert(!isnull);
+ cmd_value = DatumGetChar(value_datum);
+
+ /* Get policy name */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecpolname,
+ RelationGetDescr(catalog), &isnull);
+ Assert(!isnull);
+ policy_name_value = DatumGetCString(value_datum);
+
+ /* Get policy qual */
+ value_datum = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ RelationGetDescr(catalog), &isnull);
+ Assert(!isnull);
+ qual_value = TextDatumGetCString(value_datum);
+ qual_expr = (Expr *) stringToNode(qual_value);
+
+ policy_id = HeapTupleGetOid(tuple);
+
+ /* Find policy description for policy based on policy name.*/
+ foreach(item, rsdesc->policies)
+ {
+ temp_policy = (RowSecurityPolicy *) lfirst(item);
+
+ if (strcmp(temp_policy->policy_name, policy_name_value) == 0)
+ {
+ policy = temp_policy;
+ break;
+ }
+ }
+
+ oldcxt = MemoryContextSwitchTo(rscxt);
+
+ /*
+ * If no policy was found in the list, create a new one and add it
+ * to the list.
+ */
+ if (!policy)
+ {
+ policy = MemoryContextAllocZero(rscxt, sizeof(RowSecurityPolicy));
+ policy->policy_name = policy_name_value;
+ rsdesc->policies = lcons(policy, rsdesc->policies);
+ }
+
+ /* Set policy information by command */
+ switch (cmd_value)
+ {
+ case ROWSECURITY_CMD_ALL:
+ policy->rsall = create_row_security_entry(policy_id,
+ qual_expr, rscxt);
+ break;
+ case ROWSECURITY_CMD_SELECT:
+ policy->rsselect = create_row_security_entry(policy_id,
+ qual_expr, rscxt);
+ break;
+ case ROWSECURITY_CMD_INSERT:
+ policy->rsinsert = create_row_security_entry(policy_id,
+ qual_expr, rscxt);
+ break;
+ case ROWSECURITY_CMD_UPDATE:
+ policy->rsupdate = create_row_security_entry(policy_id,
+ qual_expr, rscxt);
+ break;
+ case ROWSECURITY_CMD_DELETE:
+ policy->rsdelete = create_row_security_entry(policy_id,
+ qual_expr, rscxt);
+ break;
+ default:
+ elog(ERROR, "Unregonized command for row-security policy");
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ pfree(qual_expr);
+ }
+ }
+ PG_CATCH();
+ {
+ if (rscxt != NULL)
+ MemoryContextDelete(rscxt);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ systable_endscan(sscan);
+ heap_close(catalog, AccessShareLock);
+
+ relation->rsdesc = rsdesc;
+ }
+
+ /*
+ * RemovePolicyById -
+ * remove a row-security policy by its OID. If a policy does not exist with
+ * the provide oid, then an error is raised.
+ *
+ * policy_id - the oid of the row-security policy.
+ */
+ void
+ RemovePolicyById(Oid policy_id)
+ {
+ Relation pg_rowsecurity_rel;
+ ScanKeyData skey;
+ SysScanDesc sscan;
+ HeapTuple tuple;
+ Relation rel;
+ Oid relid;
+
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&skey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(policy_id));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityOidIndexId, true,
+ NULL, 1, &skey);
+
+ tuple = systable_getnext(sscan);
+
+ /* If the policy exists, then remote it, otherwise raise an error. */
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for row-security %u", policy_id);
+
+ relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+ rel = heap_open(relid, AccessExclusiveLock);
+
+ simple_heap_delete(pg_rowsecurity_rel, &tuple->t_self);
+
+ CacheInvalidateRelcache(rel);
+
+ /* Clean up */
+ heap_close(rel, AccessExclusiveLock);
+ systable_endscan(sscan);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+ }
+
+ /*
+ * CreatePolicy -
+ * handles the execution of the CREATE POLICY command.
+ *
+ * stmt - the CreatePolicyStmt that describes the policy to create.
+ */
+ Oid
+ CreatePolicy(CreatePolicyStmt *stmt)
+ {
+ Relation pg_rowsecurity_rel;
+ Oid rowsec_id;
+ Relation target_table;
+ Oid table_id;
+ char rseccmd;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ Node *qual;
+ ScanKeyData skeys[3];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+ Datum values[Natts_pg_rowsecurity];
+ bool isnull[Natts_pg_rowsecurity];
+ ObjectAddress target;
+ ObjectAddress myself;
+
+ /* Parse command */
+ rseccmd = parse_row_security_command(stmt->cmd);
+
+ /* Parse the supplied clause */
+ pstate = make_parsestate(NULL);
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(isnull, 0, sizeof(isnull));
+
+ /* Get id of table. */
+ table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForCreatePolicy,
+ (void *) stmt);
+
+ /* Open target_table to build qual. No lock is necessary.*/
+ target_table = relation_open(table_id, NoLock);
+
+
+ rte = addRangeTableEntryForRelation(pstate, target_table,
+ NULL, false, false);
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ qual = transformWhereClause(pstate, copyObject(stmt->qual),
+ EXPR_KIND_ROW_SECURITY,
+ "ROW SECURITY");
+
+ /* Open pg_rowsecurity catalog */
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* Set key - row security policy name. */
+ ScanKeyInit(&skeys[0],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->policy_name));
+
+ /* Set key - row security relation id. */
+ ScanKeyInit(&skeys[1],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Set key - row security command. */
+ ScanKeyInit(&skeys[2],
+ Anum_pg_rowsecurity_rseccmd,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(rseccmd));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityRelidIndexId,
+ true, NULL, 3, skeys);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /*
+ * If the policy does not already exist, then create it. Otherwise, raise
+ * an error notifying that the policy already exists.
+ */
+ if (!HeapTupleIsValid(rsec_tuple))
+ {
+ values[Anum_pg_rowsecurity_rsecrelid - 1]
+ = ObjectIdGetDatum(table_id);
+ values[Anum_pg_rowsecurity_rsecpolname - 1]
+ = CStringGetDatum(stmt->policy_name);
+ values[Anum_pg_rowsecurity_rseccmd - 1]
+ = CharGetDatum(rseccmd);
+ values[Anum_pg_rowsecurity_rsecqual -1]
+ = CStringGetTextDatum(nodeToString(qual));
+ rsec_tuple = heap_form_tuple(RelationGetDescr(pg_rowsecurity_rel),
+ values, isnull);
+ rowsec_id = simple_heap_insert(pg_rowsecurity_rel, rsec_tuple);
+ }
+ else
+ {
+ elog(ERROR, "Table \"%s\" already has a policy named \"%s\"."
+ " Use a different name for the policy or to modify this policy"
+ " use ALTER POLICY %s ON %s USING (qual)",
+ RelationGetRelationName(target_table), stmt->policy_name,
+ RelationGetRelationName(target_table), stmt->policy_name);
+ }
+
+ /* Update Indexes */
+ CatalogUpdateIndexes(pg_rowsecurity_rel, rsec_tuple);
+
+ /* Record Dependencies */
+ target.classId = RelationRelationId;
+ target.objectId = table_id;
+ target.objectSubId = 0;
+
+ myself.classId = RowSecurityRelationId;
+ myself.objectId = rowsec_id;
+ myself.objectSubId = 0;
+
+ recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+ recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ DEPENDENCY_NORMAL);
+
+ /* Turn on relhasrowsecurity on table. */
+ if (!RelationGetForm(target_table)->relhasrowsecurity)
+ {
+ Relation class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+ HeapTuple tuple;
+
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(table_id));
+
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache look up failed for relation %u", table_id);
+
+ ((Form_pg_class) GETSTRUCT(tuple))->relhasrowsecurity = true;
+
+ simple_heap_update(class_rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(class_rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(class_rel, RowExclusiveLock);
+ }
+
+ /* Invalidate Relation Cache */
+ CacheInvalidateRelcache(target_table);
+
+ /* Clean up. */
+ heap_freetuple(rsec_tuple);
+ free_parsestate(pstate);
+ systable_endscan(sscan);
+ relation_close(target_table, NoLock);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+ return rowsec_id;
+ }
+
+ /*
+ * AlterPolicy -
+ * handles the execution of the ALTER POLICY command.
+ *
+ * stmt - the AlterPolicyStmt that describes the policy and how to alter it.
+ */
+ Oid
+ AlterPolicy(AlterPolicyStmt *stmt)
+ {
+ Relation pg_rowsecurity_rel;
+ Oid rowsec_id;
+ Relation target_table;
+ Oid table_id;
+ char rseccmd;
+ ParseState *pstate;
+ RangeTblEntry *rte;
+ Node *qual;
+ ScanKeyData skeys[3];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+ HeapTuple new_tuple;
+ Datum values[Natts_pg_rowsecurity];
+ bool isnull[Natts_pg_rowsecurity];
+ bool replaces[Natts_pg_rowsecurity];
+ ObjectAddress target;
+ ObjectAddress myself;
+
+ /* Parse command */
+ rseccmd = parse_row_security_command(stmt->cmd);
+
+ /* Get id of table. */
+ table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForCreatePolicy,
+ (void *) stmt);
+
+ target_table = relation_open(table_id, NoLock);
+
+ /* Parse the row-security clause */
+ pstate = make_parsestate(NULL);
+
+ rte = addRangeTableEntryForRelation(pstate, target_table,
+ NULL, false, false);
+
+ addRTEtoQuery(pstate, rte, false, true, true);
+
+ qual = transformWhereClause(pstate, copyObject(stmt->qual),
+ EXPR_KIND_ROW_SECURITY,
+ "ROW SECURITY");
+
+ /* zero-clear */
+ memset(values, 0, sizeof(values));
+ memset(replaces, 0, sizeof(replaces));
+ memset(isnull, 0, sizeof(isnull));
+
+ /* Find policy to update. */
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* Set key - row security policy name. */
+ ScanKeyInit(&skeys[0],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->policy_name));
+
+ /* Set key - row security relation id. */
+ ScanKeyInit(&skeys[1],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ /* Set key - row security command. */
+ ScanKeyInit(&skeys[2],
+ Anum_pg_rowsecurity_rseccmd,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(rseccmd));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityRelidIndexId,
+ true, NULL, 3, skeys);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /* If the policy exists, then alter it. Otherwise, raise an error. */
+ if (HeapTupleIsValid(rsec_tuple))
+ {
+ rowsec_id = HeapTupleGetOid(rsec_tuple);
+
+ replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+
+ values[Anum_pg_rowsecurity_rsecqual -1]
+ = CStringGetTextDatum(nodeToString(qual));
+
+ new_tuple = heap_modify_tuple(rsec_tuple,
+ RelationGetDescr(pg_rowsecurity_rel),
+ values, isnull, replaces);
+ simple_heap_update(pg_rowsecurity_rel, &new_tuple->t_self, new_tuple);
+
+ /* Update Catalog Indexes */
+ CatalogUpdateIndexes(pg_rowsecurity_rel, new_tuple);
+
+ /* Update Dependencies. */
+ deleteDependencyRecordsFor(RowSecurityRelationId, rowsec_id, false);
+
+ /* Record Dependencies */
+ target.classId = RelationRelationId;
+ target.objectId = table_id;
+ target.objectSubId = 0;
+
+ myself.classId = RowSecurityRelationId;
+ myself.objectId = rowsec_id;
+ myself.objectSubId = 0;
+
+
+ recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+ recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ DEPENDENCY_NORMAL);
+
+ heap_freetuple(new_tuple);
+ } else {
+ elog(ERROR, "policy %s for %s does not exist on table %s",
+ stmt->policy_name, stmt->cmd,
+ RelationGetRelationName(target_table));
+ }
+
+ /* Invalidate Relation Cache */
+ CacheInvalidateRelcache(target_table);
+
+ /* Clean up. */
+ free_parsestate(pstate);
+ systable_endscan(sscan);
+ relation_close(target_table, NoLock);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+
+ return rowsec_id;
+ }
+
+ /*
+ * DropPolicy -
+ * handle the execution of the DROP POLICY command.
+ *
+ * stmt - the DropPolicyStmt that describes the policy to drop.
+ */
+ void
+ DropPolicy(DropPolicyStmt *stmt)
+ {
+ Relation pg_rowsecurity_rel;
+ Oid table_id;
+ char rseccmd;
+ ScanKeyData skeys[3];
+ SysScanDesc sscan;
+ HeapTuple rsec_tuple;
+
+ /* Parse command */
+ rseccmd = parse_row_security_command(stmt->cmd);
+
+ /* Get id of target table. */
+ table_id = RangeVarGetRelidExtended(stmt->table, AccessExclusiveLock,
+ false, false,
+ RangeVarCallbackForCreatePolicy,
+ (void *) stmt);
+
+ pg_rowsecurity_rel = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+ /* Add key - row security policy name. */
+ ScanKeyInit(&skeys[0],
+ Anum_pg_rowsecurity_rsecpolname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->policy_name));
+
+ /* Add key - row security relation id. */
+ ScanKeyInit(&skeys[1],
+ Anum_pg_rowsecurity_rsecrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(table_id));
+
+ ScanKeyInit(&skeys[2],
+ Anum_pg_rowsecurity_rseccmd,
+ BTEqualStrategyNumber, F_CHAREQ,
+ CharGetDatum(rseccmd));
+
+ sscan = systable_beginscan(pg_rowsecurity_rel, RowSecurityRelidIndexId,
+ true, NULL, 3, skeys);
+
+ rsec_tuple = systable_getnext(sscan);
+
+ /*
+ * If the policy exists, then remove it. If policy does not exists and
+ * the statment uses IF EXISTS, then raise a notice. If policy does not
+ * exist and the statment does not use IF EXISTS, then raise an error.
+ */
+ if (HeapTupleIsValid(rsec_tuple))
+ {
+ ObjectAddress address;
+
+ address.classId = RowSecurityRelationId;
+ address.objectId = HeapTupleHeaderGetOid(rsec_tuple->t_data);
+ address.objectSubId = 0;
+
+ performDeletion(&address, DROP_RESTRICT, 0);
+ }
+ else
+ {
+ if (!stmt->missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("row-security policy \"%s\" does not exist on table"
+ " \"%s\" for \"%s\"",
+ stmt->policy_name, stmt->table->relname, stmt->cmd)));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("row-security policy \"%s\" does not exist on table"
+ " \"%s\" for \"%s\", skipping",
+ stmt->policy_name, stmt->table->relname, stmt->cmd)));
+ }
+ }
+
+ /* Clean up. */
+ systable_endscan(sscan);
+ heap_close(pg_rowsecurity_rel, RowExclusiveLock);
+ }
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
new file mode 100644
index d3a2044..1e3fe2d
*** a/src/backend/commands/user.c
--- b/src/backend/commands/user.c
*************** CreateRole(CreateRoleStmt *stmt)
*** 87,92 ****
--- 87,93 ----
bool createdb = false; /* Can the user create databases? */
bool canlogin = false; /* Can this user login? */
bool isreplication = false; /* Is this a replication role? */
+ bool bypassrls = false; /* Is this a row security enabled role? */
int connlimit = -1; /* maximum connections allowed */
List *addroleto = NIL; /* roles to make this a member of */
List *rolemembers = NIL; /* roles to be members of this role */
*************** CreateRole(CreateRoleStmt *stmt)
*** 106,111 ****
--- 107,113 ----
DefElem *drolemembers = NULL;
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
+ DefElem *dbypassRLS = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
*************** CreateRole(CreateRoleStmt *stmt)
*** 232,237 ****
--- 234,247 ----
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
+ else if (strcmp(defel->defname, "bypassrls") == 0)
+ {
+ if (dbypassRLS)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dbypassRLS = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
*************** CreateRole(CreateRoleStmt *stmt)
*** 267,272 ****
--- 277,284 ----
adminmembers = (List *) dadminmembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dbypassRLS)
+ bypassrls = intVal(dbypassRLS->arg) != 0;
/* Check some permissions first */
if (issuper)
*************** CreateRole(CreateRoleStmt *stmt)
*** 283,288 ****
--- 295,307 ----
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create replication users")));
}
+ else if (bypassrls)
+ {
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to change row security attribute.")));
+ }
else
{
if (!have_createrole_privilege())
*************** CreateRole(CreateRoleStmt *stmt)
*** 375,380 ****
--- 394,401 ----
new_record[Anum_pg_authid_rolvaliduntil - 1] = validUntil_datum;
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
+ new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls);
+
tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls);
/*
*************** AlterRole(AlterRoleStmt *stmt)
*** 469,474 ****
--- 490,496 ----
char *validUntil = NULL; /* time the login is valid until */
Datum validUntil_datum; /* same, as timestamptz Datum */
bool validUntil_null;
+ bool bypassrls = -1;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
*************** AlterRole(AlterRoleStmt *stmt)
*** 479,484 ****
--- 501,507 ----
DefElem *dconnlimit = NULL;
DefElem *drolemembers = NULL;
DefElem *dvalidUntil = NULL;
+ DefElem *dbypassRLS = NULL;
Oid roleid;
/* Extract options from the statement node tree */
*************** AlterRole(AlterRoleStmt *stmt)
*** 573,578 ****
--- 596,609 ----
errmsg("conflicting or redundant options")));
dvalidUntil = defel;
}
+ else if (strcmp(defel->defname, "bypassrls") == 0)
+ {
+ if (dbypassRLS)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dbypassRLS = defel;
+ }
else
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
*************** AlterRole(AlterRoleStmt *stmt)
*** 604,609 ****
--- 635,642 ----
rolemembers = (List *) drolemembers->arg;
if (dvalidUntil)
validUntil = strVal(dvalidUntil->arg);
+ if (dbypassRLS)
+ bypassrls = intVal(dbypassRLS->arg);
/*
* Scan the pg_authid relation to be certain the user exists.
*************** AlterRole(AlterRoleStmt *stmt)
*** 637,642 ****
--- 670,682 ----
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter replication users")));
}
+ else if (((Form_pg_authid) GETSTRUCT(tuple))->rolbypassrls || bypassrls >= 0)
+ {
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to alter row security attribute")));
+ }
else if (!have_createrole_privilege())
{
if (!(inherit < 0 &&
*************** AlterRole(AlterRoleStmt *stmt)
*** 770,775 ****
--- 810,821 ----
new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = validUntil_null;
new_record_repl[Anum_pg_authid_rolvaliduntil - 1] = true;
+ if (bypassrls >= 0)
+ {
+ new_record[Anum_pg_authid_rolbypassrls - 1] = BoolGetDatum(bypassrls > 0);
+ new_record_repl[Anum_pg_authid_rolbypassrls - 1] = true;
+ }
+
new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record,
new_record_nulls, new_record_repl);
simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 3088578..ccbfdd8
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAlterTSConfigurationStmt(const Alte
*** 3849,3854 ****
--- 3849,3892 ----
return newnode;
}
+ static CreatePolicyStmt *
+ _copyCreatePolicyStmt(const CreatePolicyStmt *from)
+ {
+ CreatePolicyStmt *newnode = makeNode(CreatePolicyStmt);
+
+ COPY_STRING_FIELD(policy_name);
+ COPY_NODE_FIELD(table);
+ COPY_SCALAR_FIELD(cmd);
+ COPY_NODE_FIELD(qual);
+
+ return newnode;
+ }
+
+ static AlterPolicyStmt *
+ _copyAlterPolicyStmt(const AlterPolicyStmt *from)
+ {
+ AlterPolicyStmt *newnode = makeNode(AlterPolicyStmt);
+
+ COPY_STRING_FIELD(policy_name);
+ COPY_NODE_FIELD(table);
+ COPY_SCALAR_FIELD(cmd);
+ COPY_NODE_FIELD(qual);
+
+ return newnode;
+ }
+
+ static DropPolicyStmt *
+ _copyDropPolicyStmt(const DropPolicyStmt *from)
+ {
+ DropPolicyStmt *newnode = makeNode(DropPolicyStmt);
+
+ COPY_STRING_FIELD(policy_name);
+ COPY_NODE_FIELD(table);
+ COPY_SCALAR_FIELD(cmd);
+
+ return newnode;
+ }
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
*************** copyObject(const void *from)
*** 4561,4566 ****
--- 4599,4613 ----
case T_AlterTSConfigurationStmt:
retval = _copyAlterTSConfigurationStmt(from);
break;
+ case T_CreatePolicyStmt:
+ retval = _copyCreatePolicyStmt(from);
+ break;
+ case T_AlterPolicyStmt:
+ retval = _copyAlterPolicyStmt(from);
+ break;
+ case T_DropPolicyStmt:
+ retval = _copyDropPolicyStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 1b07db6..e8b4226
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAlterTSConfigurationStmt(const Alt
*** 2008,2013 ****
--- 2008,2045 ----
}
static bool
+ _equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b)
+ {
+ COMPARE_STRING_FIELD(policy_name);
+ COMPARE_NODE_FIELD(table);
+ COMPARE_SCALAR_FIELD(cmd);
+ COMPARE_NODE_FIELD(qual);
+
+ return true;
+ }
+
+ static bool
+ _equalAlterPolicyStmt(const AlterPolicyStmt *a, const AlterPolicyStmt *b)
+ {
+ COMPARE_STRING_FIELD(policy_name);
+ COMPARE_NODE_FIELD(table);
+ COMPARE_SCALAR_FIELD(cmd);
+ COMPARE_NODE_FIELD(qual);
+
+ return true;
+ }
+
+ static bool
+ _equalDropPolicyStmt(const DropPolicyStmt *a, const DropPolicyStmt *b)
+ {
+ COMPARE_STRING_FIELD(policy_name);
+ COMPARE_NODE_FIELD(table);
+ COMPARE_SCALAR_FIELD(cmd);
+
+ return true;
+ }
+
+ static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
COMPARE_SCALAR_FIELD(kind);
*************** equal(const void *a, const void *b)
*** 3025,3030 ****
--- 3057,3071 ----
case T_AlterTSConfigurationStmt:
retval = _equalAlterTSConfigurationStmt(a, b);
break;
+ case T_CreatePolicyStmt:
+ retval = _equalCreatePolicyStmt(a, b);
+ break;
+ case T_AlterPolicyStmt:
+ retval = _equalAlterPolicyStmt(a, b);
+ break;
+ case T_DropPolicyStmt:
+ retval = _equalDropPolicyStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index a113809..311b658
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 231,237 ****
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
--- 231,237 ----
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
*************** static Node *makeRecursiveViewSelect(cha
*** 240,249 ****
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
! DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
--- 240,249 ----
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
! DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt DropPolicyStmt
DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
*************** static Node *makeRecursiveViewSelect(cha
*** 269,274 ****
--- 269,275 ----
%type alter_table_cmd alter_type_cmd opt_collate_clause
replica_identity
%type alter_table_cmds alter_type_cmds
+ %type row_security_cmd
%type opt_drop_behavior
*************** static Node *makeRecursiveViewSelect(cha
*** 319,325 ****
%type all_Op MathOp
! %type iso_level opt_encoding
%type grantee
%type grantee_list
%type privilege
--- 320,326 ----
%type all_Op MathOp
! %type iso_level opt_encoding row_security_option
%type grantee
%type grantee_list
%type privilege
*************** static Node *makeRecursiveViewSelect(cha
*** 588,594 ****
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
! PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
--- 589,595 ----
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
! PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY POSITION
PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
*************** stmt :
*** 739,744 ****
--- 740,746 ----
| AlterGroupStmt
| AlterObjectSchemaStmt
| AlterOwnerStmt
+ | AlterPolicyStmt
| AlterSeqStmt
| AlterSystemStmt
| AlterTableStmt
*************** stmt :
*** 773,778 ****
--- 775,781 ----
| CreateOpClassStmt
| CreateOpFamilyStmt
| AlterOpFamilyStmt
+ | CreatePolicyStmt
| CreatePLangStmt
| CreateSchemaStmt
| CreateSeqStmt
*************** stmt :
*** 798,803 ****
--- 801,807 ----
| DropOpClassStmt
| DropOpFamilyStmt
| DropOwnedStmt
+ | DropPolicyStmt
| DropPLangStmt
| DropRuleStmt
| DropStmt
*************** AlterOptRoleElem:
*** 956,961 ****
--- 960,969 ----
$$ = makeDefElem("canlogin", (Node *)makeInteger(TRUE));
else if (strcmp($1, "nologin") == 0)
$$ = makeDefElem("canlogin", (Node *)makeInteger(FALSE));
+ else if (strcmp($1, "bypassrls") == 0)
+ $$ = makeDefElem("bypassrls", (Node *)makeInteger(TRUE));
+ else if (strcmp($1, "nobypassrls") == 0)
+ $$ = makeDefElem("bypassrls", (Node *)makeInteger(FALSE));
else if (strcmp($1, "noinherit") == 0)
{
/*
*************** set_rest_more: /* Generic SET syntaxes:
*** 1472,1477 ****
--- 1480,1493 ----
n->args = list_make1(makeStringConst($3, @3));
$$ = n;
}
+ | ROW SECURITY row_security_option
+ {
+ VariableSetStmt *n = makeNode(VariableSetStmt);
+ n->kind = VAR_SET_VALUE;
+ n->name = "row_security";
+ n->args = list_make1(makeStringConst($3, @3));
+ $$ = n;
+ }
;
var_name: ColId { $$ = $1; }
*************** var_value: opt_boolean_or_string
*** 1489,1494 ****
--- 1505,1515 ----
{ $$ = makeAConst($1, @1); }
;
+ row_security_option:
+ ON { $$ = "on"; }
+ | OFF { $$ = "off"; }
+ ;
+
iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| READ COMMITTED { $$ = "read committed"; }
| REPEATABLE READ { $$ = "repeatable read"; }
*************** AlterUserMappingStmt: ALTER USER MAPPING
*** 4397,4402 ****
--- 4418,4485 ----
/*****************************************************************************
*
+ * QUERIES:
+ * CREATE POLICY name ON table FOR cmd USING (qual)
+ * ALTER POLICY name ON table FOR cmd USING (qual)
+ * DROP POLICY name ON table FOR cmd
+ *
+ *****************************************************************************/
+
+ CreatePolicyStmt:
+ CREATE POLICY name ON qualified_name FOR row_security_cmd USING '(' a_expr ')'
+ {
+ CreatePolicyStmt *n = makeNode(CreatePolicyStmt);
+ n->policy_name = $3;
+ n->table = $5;
+ n->cmd = $7;
+ n->qual = $10;
+ $$ = (Node *) n;
+ }
+ ;
+
+ AlterPolicyStmt:
+ ALTER POLICY name ON qualified_name FOR row_security_cmd USING '(' a_expr ')'
+ {
+ AlterPolicyStmt *n = makeNode(AlterPolicyStmt);
+ n->policy_name = $3;
+ n->table = $5;
+ n->cmd = $7;
+ n->qual = $10;
+ $$ = (Node *) n;
+ }
+ ;
+
+ DropPolicyStmt:
+ DROP POLICY name ON qualified_name FOR row_security_cmd
+ {
+ DropPolicyStmt *n = makeNode(DropPolicyStmt);
+ n->policy_name = $3;
+ n->table = $5;
+ n->cmd = $7;
+ n->missing_ok = FALSE;
+ $$ = (Node *) n;
+ }
+ | DROP POLICY IF_P EXISTS name ON qualified_name FOR row_security_cmd
+ {
+ DropPolicyStmt *n = makeNode(DropPolicyStmt);
+ n->policy_name = $5;
+ n->table = $7;
+ n->cmd = $9;
+ n->missing_ok = TRUE;
+ $$ = (Node *) n;
+ }
+ ;
+
+ row_security_cmd:
+ ALL { $$ = "all"; }
+ | SELECT { $$ = "select"; }
+ | INSERT { $$ = "insert"; }
+ | UPDATE { $$ = "update"; }
+ | DELETE_P { $$ = "delete"; }
+ ;
+
+ /*****************************************************************************
+ *
* QUERIES :
* CREATE TRIGGER ...
* DROP TRIGGER ...
*************** unreserved_keyword:
*** 13026,13031 ****
--- 13109,13115 ----
| PASSING
| PASSWORD
| PLANS
+ | POLICY
| PRECEDING
| PREPARE
| PREPARED
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
new file mode 100644
index c984b7d..bdd2a3d
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** transformAggregateCall(ParseState *pstat
*** 333,338 ****
--- 333,341 ----
case EXPR_KIND_TRIGGER_WHEN:
err = _("aggregate functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_ROW_SECURITY:
+ err = _("aggregate functions are not allowed in row-security policy");
+ break;
/*
* There is intentionally no default: case here, so that the
*************** transformWindowFuncCall(ParseState *psta
*** 662,667 ****
--- 665,673 ----
case EXPR_KIND_TRIGGER_WHEN:
err = _("window functions are not allowed in trigger WHEN conditions");
break;
+ case EXPR_KIND_ROW_SECURITY:
+ err = _("window functions are not allowed in row-security policy");
+ break;
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
new file mode 100644
index 4a8aaf6..7848e3b
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformSubLink(ParseState *pstate, Sub
*** 1530,1535 ****
--- 1530,1536 ----
case EXPR_KIND_OFFSET:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
+ case EXPR_KIND_ROW_SECURITY:
/* okay */
break;
case EXPR_KIND_CHECK_CONSTRAINT:
*************** ParseExprKindName(ParseExprKind exprKind
*** 2702,2707 ****
--- 2703,2710 ----
return "EXECUTE";
case EXPR_KIND_TRIGGER_WHEN:
return "WHEN";
+ case EXPR_KIND_ROW_SECURITY:
+ return "ROW SECURITY";
/*
* There is intentionally no default: case here, so that the
diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile
new file mode 100644
index 9ff56c7..25423d3
*** a/src/backend/rewrite/Makefile
--- b/src/backend/rewrite/Makefile
*************** top_builddir = ../../..
*** 13,18 ****
include $(top_builddir)/src/Makefile.global
OBJS = rewriteRemove.o rewriteDefine.o \
! rewriteHandler.o rewriteManip.o rewriteSupport.o
include $(top_srcdir)/src/backend/common.mk
--- 13,19 ----
include $(top_builddir)/src/Makefile.global
OBJS = rewriteRemove.o rewriteDefine.o \
! rewriteHandler.o rewriteManip.o rewriteSupport.o \
! rowsecurity.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index e6c5530..19cd36f
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 25,30 ****
--- 25,31 ----
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
+ #include "rewrite/rowsecurity.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
*************** fireRIRrules(Query *parsetree, List *act
*** 1670,1715 ****
* Collect the RIR rules that we must apply
*/
rules = rel->rd_rules;
! if (rules == NULL)
! {
! heap_close(rel, NoLock);
! continue;
! }
! locks = NIL;
! for (i = 0; i < rules->numLocks; i++)
{
! rule = rules->rules[i];
! if (rule->event != CMD_SELECT)
! continue;
! locks = lappend(locks, rule);
! }
/*
! * If we found any, apply them --- but first check for recursion!
*/
! if (locks != NIL)
{
! ListCell *l;
!
if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
! errmsg("infinite recursion detected in rules for relation \"%s\"",
! RelationGetRelationName(rel))));
activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
! foreach(l, locks)
! {
! rule = lfirst(l);
!
! parsetree = ApplyRetrieveRule(parsetree,
! rule,
! rt_index,
! rel,
! activeRIRs,
! forUpdatePushedDown);
! }
activeRIRs = list_delete_first(activeRIRs);
}
--- 1671,1733 ----
* Collect the RIR rules that we must apply
*/
rules = rel->rd_rules;
! if (rules != NULL)
{
! locks = NIL;
! for (i = 0; i < rules->numLocks; i++)
! {
! rule = rules->rules[i];
! if (rule->event != CMD_SELECT)
! continue;
! locks = lappend(locks, rule);
! }
!
! /*
! * If we found any, apply them --- but first check for recursion!
! */
! if (locks != NIL)
! {
! ListCell *l;
!
! if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
! errmsg("infinite recursion detected in rules for relation \"%s\"",
! RelationGetRelationName(rel))));
! activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
!
! foreach(l, locks)
! {
! rule = lfirst(l);
+ parsetree = ApplyRetrieveRule(parsetree,
+ rule,
+ rt_index,
+ rel,
+ activeRIRs,
+ forUpdatePushedDown);
+ }
+
+ activeRIRs = list_delete_first(activeRIRs);
+ }
+ }
/*
! * If the RTE has row-security quals, apply them and recurse into the
! * securityQuals.
*/
! if (prepend_row_security_quals(parsetree, rte, rt_index))
{
! // We applied security quals, check for infinite recursion and
! // then expand any nested queries.
if (list_member_oid(activeRIRs, RelationGetRelid(rel)))
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
! errmsg("infinite recursion detected in row-security policy for relation \"%s\"",
! RelationGetRelationName(rel))));
activeRIRs = lcons_oid(RelationGetRelid(rel), activeRIRs);
! expression_tree_walker( (Node*) rte->securityQuals, fireRIRonSubLink, (void*)activeRIRs );
activeRIRs = list_delete_first(activeRIRs);
}
diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c
new file mode 100644
index ...b25af1d
*** a/src/backend/rewrite/rowsecurity.c
--- b/src/backend/rewrite/rowsecurity.c
***************
*** 0 ****
--- 1,193 ----
+ /*
+ * rewrite/rowsecurity.c
+ * Routines to support row-security feature
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+ #include "postgres.h"
+
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_inherits_fn.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
+ #include "nodes/nodeFuncs.h"
+ #include "nodes/pg_list.h"
+ #include "nodes/plannodes.h"
+ #include "parser/parsetree.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "rewrite/rewriteManip.h"
+ #include "rewrite/rowsecurity.h"
+ #include "utils/acl.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+
+ /* hook to allow extensions to apply their own security policy */
+ row_security_policy_hook_type row_security_policy_hook = NULL;
+
+ /*
+ * Check the given RTE to see whether it's already had row-security quals
+ * expanded and, if not, prepend any row-security rules from built-in or
+ * plug-in sources to the securityQuals. The security quals are rewritten (for
+ * view expansion, etc) before being added to the RTE.
+ *
+ * Returns true if any quals were added. Note that quals may have been found
+ * but not added if user rights make the user exempt from row security.
+ */
+ bool
+ prepend_row_security_quals(Query* root, RangeTblEntry* rte, int rt_index)
+ {
+ List *rowsecquals;
+ Relation rel;
+ Oid userid;
+ int sec_context;
+ bool qualsAdded = false;
+
+ GetUserIdAndSecContext(&userid, &sec_context);
+
+ if (rte->relid >= FirstNormalObjectId
+ && rte->relkind == RELKIND_RELATION
+ && !(sec_context & SECURITY_ROW_LEVEL_DISABLED))
+ {
+ /*
+ * Fetch the row-security qual and add it to the list of quals
+ * to be expanded by expand_security_quals.
+ */
+ rel = heap_open(rte->relid, NoLock);
+ rowsecquals = pull_row_security_policy(root->commandType, rel);
+ if (rowsecquals)
+ {
+ /*
+ * Row security quals always have the target table as varno 1, as no
+ * joins are permitted in row security expressions. We must walk
+ * the expression, updating any references to varno 1 to the varno
+ * the table has in the outer query.
+ *
+ * We rewrite the expression in-place.
+ */
+ qualsAdded = true;
+ ChangeVarNodes((Node *) rowsecquals, 1, rt_index, 0);
+ rte->securityQuals = list_concat(rowsecquals, rte->securityQuals);
+ }
+ heap_close(rel, NoLock);
+ }
+
+ return qualsAdded;
+ }
+
+ /*
+ * pull_row_security_policy
+ *
+ * Fetches the configured row-security policy of both built-in catalogs and any
+ * extensions. If any policy is found a list of qualifier expressions is
+ * returned, where each is treated as a securityQual.
+ *
+ * Vars must use varno 1 to refer to the table with row security.
+ *
+ * The returned expression trees will be modified in-place, so return copies if
+ * you're not generating the expression tree each time.
+ */
+ List *
+ pull_row_security_policy(CmdType cmd, Relation relation)
+ {
+ List *quals = NIL;
+
+ /*
+ * Pull the row-security policy configured with built-in features,
+ * if unprivileged users. Please note that superuser can bypass it.
+ */
+ if (relation->rsdesc)
+ {
+ /*
+ * If Row Security is enabled, then it is applied to all queries on the
+ * relation. If Row Security is disabled, then we must check that the
+ * current user has the privilege to bypass. If the current user does
+ * not have the ability to bypass, then an error is thrown.
+ */
+ if (is_rls_enabled())
+ {
+ ListCell *item;
+ RowSecurityPolicy *policy;
+
+ foreach(item, relation->rsdesc->policies)
+ {
+ policy = (RowSecurityPolicy *) lfirst(item);
+
+ /* Always add ALL policy if exists. */
+ if (policy->rsall != NULL)
+ quals = lcons(copyObject(policy->rsall->qual), quals);
+
+ /* Build list of quals, merging when appropriate. */
+ switch(cmd)
+ {
+ case CMD_SELECT:
+ if (policy->rsselect != NULL)
+ quals = lcons(copyObject(policy->rsselect->qual), quals);
+ break;
+ case CMD_INSERT:
+ if (policy->rsinsert != NULL)
+ quals = lcons(copyObject(policy->rsinsert->qual), quals);
+ break;
+ case CMD_UPDATE:
+ if (policy->rsselect != NULL)
+ quals = lcons(copyObject(policy->rsselect->qual), quals);
+ if (policy->rsupdate != NULL)
+ quals = lcons(copyObject(policy->rsupdate->qual), quals);
+ break;
+ case CMD_DELETE:
+ if (policy->rsselect != NULL)
+ quals = lcons(copyObject(policy->rsselect->qual), quals);
+ if (policy->rsdelete != NULL)
+ quals = lcons(copyObject(policy->rsdelete->qual), quals);
+ break;
+ default:
+ elog(ERROR, "unrecognized command type.");
+ break;
+ }
+ }
+ }
+ else if (!has_bypassrls_privilege(GetUserId()))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("Insufficient privilege to bypass row security.")));
+ }
+
+
+ /*
+ * Also, ask extensions whether they want to apply their own
+ * row-security policy. If both built-in and extension has
+ * their own policy they're applied as nested qualifiers.
+ */
+ if (row_security_policy_hook)
+ {
+ List *temp;
+
+ temp = (*row_security_policy_hook)(cmd, relation);
+ if (temp != NIL)
+ quals = lcons(temp, quals);
+ }
+
+ return quals;
+ }
+
+ /*
+ * is_rls_enabled -
+ * determines if row-security is enabled by checking the value of the system
+ * configuration "row_security".
+ */
+ bool
+ is_rls_enabled()
+ {
+ char const *rls_option;
+
+ rls_option = GetConfigOption("row_security", true, false);
+
+ return (strcmp(rls_option, "on") == 0);
+ }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
new file mode 100644
index 07e0b98..24a85bb
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 39,44 ****
--- 39,45 ----
#include "commands/extension.h"
#include "commands/matview.h"
#include "commands/lockcmds.h"
+ #include "commands/policy.h"
#include "commands/portalcmds.h"
#include "commands/prepare.h"
#include "commands/proclang.h"
*************** standard_ProcessUtility(Node *parsetree,
*** 837,842 ****
--- 838,853 ----
}
break;
+ case T_CreatePolicyStmt:
+ CreatePolicy((CreatePolicyStmt *) parsetree);
+ break;
+ case T_AlterPolicyStmt:
+ AlterPolicy((AlterPolicyStmt *) parsetree);
+ break;
+ case T_DropPolicyStmt:
+ DropPolicy((DropPolicyStmt *) parsetree);
+ break;
+
default:
/* All other statement types have event trigger support */
ProcessUtilitySlow(parsetree, queryString,
*************** CreateCommandTag(Node *parsetree)
*** 2438,2443 ****
--- 2449,2464 ----
}
break;
+ case T_CreatePolicyStmt:
+ tag = "CREATE POLICY";
+ break;
+ case T_AlterPolicyStmt:
+ tag = "ALTER POLICY";
+ break;
+ case T_DropPolicyStmt:
+ tag = "DROP POLICY";
+ break;
+
default:
elog(WARNING, "unrecognized node type: %d",
(int) nodeTag(parsetree));
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index e4d7b2c..9d32e18
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** ri_PlanCheck(const char *querystr, int n
*** 2956,2961 ****
--- 2956,2962 ----
Relation query_rel;
Oid save_userid;
int save_sec_context;
+ int temp_sec_context;
/*
* Use the query type code to determine whether the query is run against
*************** ri_PlanCheck(const char *querystr, int n
*** 2968,2975 ****
/* Switch to proper UID to perform check as */
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
/* Create the plan */
qplan = SPI_prepare(querystr, nargs, argtypes);
--- 2969,2986 ----
/* Switch to proper UID to perform check as */
GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+ /*
+ * Row-level security should be disabled in the case where a foreign-key
+ * relation is queried to check existence of tuples that references the
+ * primary-key being modified.
+ */
+ temp_sec_context = save_sec_context | SECURITY_LOCAL_USERID_CHANGE;
+ if (qkey->constr_queryno != RI_PLAN_CHECK_LOOKUPPK)
+ temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! temp_sec_context);
/* Create the plan */
qplan = SPI_prepare(querystr, nargs, argtypes);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index e4697f6..48bf9a8
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 50,60 ****
--- 50,62 ----
#include "catalog/pg_opclass.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
+ #include "catalog/pg_rowsecurity.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/schemapg.h"
#include "catalog/storage.h"
+ #include "commands/policy.h"
#include "commands/trigger.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
*************** RelationBuildDesc(Oid targetRelId, bool
*** 966,971 ****
--- 968,978 ----
else
relation->trigdesc = NULL;
+ if (relation->rd_rel->relhasrowsecurity)
+ RelationBuildRowSecurity(relation);
+ else
+ relation->rsdesc = NULL;
+
/*
* if it's an index, initialize index-related information
*/
*************** RelationDestroyRelation(Relation relatio
*** 1936,1941 ****
--- 1943,1950 ----
MemoryContextDelete(relation->rd_indexcxt);
if (relation->rd_rulescxt)
MemoryContextDelete(relation->rd_rulescxt);
+ if (relation->rsdesc)
+ MemoryContextDelete(relation->rsdesc->rscxt);
if (relation->rd_fdwroutine)
pfree(relation->rd_fdwroutine);
pfree(relation);
*************** RelationCacheInitializePhase3(void)
*** 3334,3339 ****
--- 3343,3356 ----
restart = true;
}
+ if (relation->rd_rel->relhasrowsecurity && relation->rsdesc == NULL)
+ {
+ RelationBuildRowSecurity(relation);
+ if (relation->rsdesc == NULL)
+ relation->rd_rel->relhasrowsecurity = false;
+ restart = true;
+ }
+
/* Release hold on the relation */
RelationDecrementReferenceCount(relation);
*************** load_relcache_init_file(bool shared)
*** 4702,4707 ****
--- 4719,4725 ----
rel->rd_rules = NULL;
rel->rd_rulescxt = NULL;
rel->trigdesc = NULL;
+ rel->rsdesc = NULL;
rel->rd_indexprs = NIL;
rel->rd_indpred = NIL;
rel->rd_exclops = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index 60e4354..67deb32
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
***************
*** 31,36 ****
--- 31,37 ----
#include "access/twophase.h"
#include "access/xact.h"
#include "catalog/namespace.h"
+ #include "catalog/pg_rowsecurity.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/vacuum.h"
*************** static const struct config_enum_entry hu
*** 401,406 ****
--- 402,423 ----
};
/*
+ * Although only "on" and"off" are documented, we accept all likely variants of
+ * "on" and "off".
+ */
+ static const struct config_enum_entry row_security_options[] = {
+ {"off", ROW_SECURITY_OFF, false},
+ {"on", ROW_SECURITY_ON, false},
+ {"true", ROW_SECURITY_ON, true},
+ {"false", ROW_SECURITY_OFF, true},
+ {"yes", ROW_SECURITY_ON, true},
+ {"no", ROW_SECURITY_OFF, true},
+ {"1", ROW_SECURITY_ON, true},
+ {"0", ROW_SECURITY_OFF, true},
+ {NULL, 0, false}
+ };
+
+ /*
* Options for enum values stored in other modules
*/
extern const struct config_enum_entry wal_level_options[];
*************** int tcp_keepalives_idle;
*** 456,461 ****
--- 473,480 ----
int tcp_keepalives_interval;
int tcp_keepalives_count;
+ int row_security;
+
/*
* This really belongs in pg_shmem.c, but is defined here so that it doesn't
* need to be duplicated in all the different implementations of pg_shmem.c.
*************** static struct config_enum ConfigureNames
*** 3384,3390 ****
LOGSTMT_NONE, log_statement_options,
NULL, NULL, NULL
},
!
{
{"syslog_facility", PGC_SIGHUP, LOGGING_WHERE,
gettext_noop("Sets the syslog \"facility\" to be used when syslog enabled."),
--- 3403,3419 ----
LOGSTMT_NONE, log_statement_options,
NULL, NULL, NULL
},
! {
! {"row_security", PGC_USERSET, CONN_AUTH_SECURITY,
! gettext_noop("Enable row security."),
! gettext_noop("When enabled, row security will be applied to all "
! "users except those with superuser privileges."
! )
! },
! &row_security,
! ROW_SECURITY_ON, row_security_options,
! NULL, NULL, NULL
! },
{
{"syslog_facility", PGC_SIGHUP, LOGGING_WHERE,
gettext_noop("Sets the syslog \"facility\" to be used when syslog enabled."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
new file mode 100644
index 3f3e706..3c47fd2
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 90,95 ****
--- 90,96 ----
#ssl_crl_file = '' # (change requires restart)
#password_encryption = on
#db_user_namespace = off
+ #row_security = on
# GSSAPI using Kerberos
#krb_server_keyfile = ''
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 94e9147..2f855cf
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
*************** getSchemaData(Archive *fout, int *numTab
*** 244,249 ****
--- 244,253 ----
write_msg(NULL, "reading rewrite rules\n");
getRules(fout, &numRules);
+ if (g_verbose)
+ write_msg(NULL, "reading row-security policies\n");
+ getRowSecurity(fout, tblinfo, numTables);
+
*numTablesPtr = numTables;
return tblinfo;
}
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
new file mode 100644
index 3aebac8..427e3d9
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3228,3233 ****
--- 3228,3234 ----
strcmp(te->desc, "INDEX") == 0 ||
strcmp(te->desc, "RULE") == 0 ||
strcmp(te->desc, "TRIGGER") == 0 ||
+ strcmp(te->desc, "ROW SECURITY") == 0 ||
strcmp(te->desc, "USER MAPPING") == 0)
{
/* these object types don't have separate owners */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
new file mode 100644
index 5c0f95f..3b42caa
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static int no_security_labels = 0;
*** 137,142 ****
--- 137,143 ----
static int no_synchronized_snapshots = 0;
static int no_unlogged_table_data = 0;
static int serializable_deferrable = 0;
+ static int allow_row_security = 0;
static void help(const char *progname);
*************** static char *myFormatType(const char *ty
*** 247,252 ****
--- 248,254 ----
static void getBlobs(Archive *fout);
static void dumpBlob(Archive *fout, BlobInfo *binfo);
static int dumpBlobs(Archive *fout, void *arg);
+ static void dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo);
static void dumpDatabase(Archive *AH);
static void dumpEncoding(Archive *AH);
static void dumpStdStrings(Archive *AH);
*************** main(int argc, char **argv)
*** 340,345 ****
--- 342,348 ----
/*
* the following options don't have an equivalent short option letter
*/
+ {"allow-row-security", no_argument, &allow_row_security, 1},
{"attribute-inserts", no_argument, &column_inserts, 1},
{"binary-upgrade", no_argument, &binary_upgrade, 1},
{"column-inserts", no_argument, &column_inserts, 1},
*************** help(const char *progname)
*** 893,898 ****
--- 896,902 ----
printf(_(" -t, --table=TABLE dump the named table(s) only\n"));
printf(_(" -T, --exclude-table=TABLE do NOT dump the named table(s)\n"));
printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n"));
+ printf(_(" --allow-row-security allow execution of row level security\n"));
printf(_(" --binary-upgrade for use by upgrade utilities only\n"));
printf(_(" --column-inserts dump data as INSERT commands with column names\n"));
printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n"));
*************** setup_connection(Archive *AH, const char
*** 1050,1055 ****
--- 1054,1064 ----
else
AH->sync_snapshot_id = get_synchronized_snapshot(AH);
}
+
+ if (allow_row_security)
+ ExecuteSqlStatement(AH, "SET ROW SECURITY ON");
+ else
+ ExecuteSqlStatement(AH, "SET ROW SECURITY OFF");
}
static void
*************** dumpBlobs(Archive *fout, void *arg)
*** 2756,2761 ****
--- 2765,2902 ----
return 1;
}
+ /*
+ * getRowSecurity
+ * get information about every row-security policy on a dumpable table.
+ */
+ void
+ getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables)
+ {
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ RowSecurityInfo *rsinfo;
+ int i_oid;
+ int i_tableoid;
+ int i_rsecpolname;
+ int i_rseccmd;
+ int i_rsecqual;
+ int i, j, ntups;
+
+ if (fout->remoteVersion < 90500)
+ return;
+
+ for (i = 0; i < numTables; i++)
+ {
+ TableInfo *tbinfo = &tblinfo[i];
+
+ if (!tbinfo->hasrowsec || !tbinfo->dobj.dump)
+ continue;
+
+ if (g_verbose)
+ write_msg(NULL, "reading row-security policy for table \"%s\"\n",
+ tbinfo->dobj.name);
+
+ /*
+ * select table schema to ensure regproc name is qualified if needed
+ */
+ selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name);
+
+ resetPQExpBuffer(query);
+
+ appendPQExpBuffer(query,
+ "SELECT oid, tableoid, s.rsecpolname, s.rseccmd, "
+ "pg_get_expr(s.rsecqual, s.rsecrelid) AS rsecqual "
+ "FROM pg_catalog.pg_rowsecurity s "
+ "WHERE rsecrelid = '%u'",
+ tbinfo->dobj.catId.oid);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ i_oid = PQfnumber(res, "oid");
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_rsecpolname = PQfnumber(res, "rsecpolname");
+ i_rseccmd = PQfnumber(res, "rseccmd");
+ i_rsecqual = PQfnumber(res, "rsecqual");
+
+ rsinfo = pg_malloc(ntups * sizeof(RowSecurityInfo));
+
+ for (j = 0; j < ntups; j++)
+ {
+ char namebuf[NAMEDATALEN + 1];
+
+ rsinfo[j].dobj.objType = DO_ROW_SECURITY;
+ rsinfo[j].dobj.catId.tableoid =
+ atooid(PQgetvalue(res, j, i_tableoid));
+ rsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
+ AssignDumpId(&rsinfo[j].dobj);
+ snprintf(namebuf, sizeof(namebuf), "row-security of %s",
+ tbinfo->rolname);
+ rsinfo[j].dobj.name = namebuf;
+ rsinfo[j].dobj.namespace = tbinfo->dobj.namespace;
+ rsinfo[j].rstable = tbinfo;
+ rsinfo[j].rsecpolname = pg_strdup(PQgetvalue(res, j, i_rsecpolname));
+ rsinfo[j].rseccmd = pg_strdup(PQgetvalue(res, j, i_rseccmd));
+ rsinfo[j].rsecqual = pg_strdup(PQgetvalue(res, j, i_rsecqual));
+ }
+ PQclear(res);
+ }
+ destroyPQExpBuffer(query);
+ }
+
+ /*
+ * dumpRowSecurity
+ * dump the definition of the given row-security policy
+ */
+ static void
+ dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo)
+ {
+ TableInfo *tbinfo = rsinfo->rstable;
+ PQExpBuffer query;
+ PQExpBuffer delqry;
+ const char *cmd;
+
+ if (dataOnly || !tbinfo->hasrowsec)
+ return;
+
+ if (strcmp(rsinfo->rseccmd, "a") == 0)
+ cmd = "ALL";
+ else if (strcmp(rsinfo->rseccmd, "s") == 0)
+ cmd = "SELECT";
+ else if (strcmp(rsinfo->rseccmd, "i") == 0)
+ cmd = "INSERT";
+ else if (strcmp(rsinfo->rseccmd, "u") == 0)
+ cmd = "UPDATE";
+ else if (strcmp(rsinfo->rseccmd, "d") == 0)
+ cmd = "DELETE";
+ else
+ {
+ write_msg(NULL, "unexpected command type: '%s'\n", rsinfo->rseccmd);
+ exit_nicely(1);
+ }
+
+ query = createPQExpBuffer();
+ delqry = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "CREATE POLICY %s ON %s FOR %s USING %s;\n",
+ rsinfo->rsecpolname, fmtId(tbinfo->dobj.name),
+ cmd, rsinfo->rsecqual);
+ appendPQExpBuffer(delqry, "DROP POLICY %s ON %s FOR %s;\n",
+ rsinfo->rsecpolname, fmtId(tbinfo->dobj.name), cmd);
+
+ ArchiveEntry(fout, rsinfo->dobj.catId, rsinfo->dobj.dumpId,
+ rsinfo->dobj.name,
+ rsinfo->dobj.namespace->dobj.name,
+ NULL,
+ tbinfo->rolname, false,
+ "ROW SECURITY", SECTION_POST_DATA,
+ query->data, delqry->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ destroyPQExpBuffer(query);
+ }
+
static void
binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
PQExpBuffer upgrade_buffer,
*************** getTables(Archive *fout, int *numTables)
*** 4286,4291 ****
--- 4427,4433 ----
int i_relhastriggers;
int i_relhasindex;
int i_relhasrules;
+ int i_relhasrowsec;
int i_relhasoids;
int i_relfrozenxid;
int i_relminmxid;
*************** getTables(Archive *fout, int *numTables)
*** 4327,4333 ****
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
! if (fout->remoteVersion >= 90400)
{
/*
* Left join to pick up dependency info linking sequences to their
--- 4469,4475 ----
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
! if (fout->remoteVersion >= 90500)
{
/*
* Left join to pick up dependency info linking sequences to their
*************** getTables(Archive *fout, int *numTables)
*** 4339,4344 ****
--- 4481,4528 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relhasrowsecurity, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype = 'a') "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ username_subquery,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE);
+ }
+ else if (fout->remoteVersion >= 90400)
+ {
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ */
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "c.relacl, c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4379,4384 ****
--- 4563,4569 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"tc.relminmxid AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4419,4424 ****
--- 4604,4610 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4457,4462 ****
--- 4643,4649 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4494,4499 ****
--- 4681,4687 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, c.relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4531,4536 ****
--- 4719,4725 ----
"(%s c.relowner) AS rolname, "
"c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
"c.relhasindex, c.relhasrules, c.relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, "
"0 AS tminmxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4568,4573 ****
--- 4757,4763 ----
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
*************** getTables(Archive *fout, int *numTables)
*** 4604,4609 ****
--- 4794,4800 ----
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
*************** getTables(Archive *fout, int *numTables)
*** 4636,4641 ****
--- 4827,4833 ----
"(%s relowner) AS rolname, "
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
*************** getTables(Archive *fout, int *numTables)
*** 4663,4668 ****
--- 4855,4861 ----
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
*************** getTables(Archive *fout, int *numTables)
*** 4700,4705 ****
--- 4893,4899 ----
"relchecks, (reltriggers <> 0) AS relhastriggers, "
"relhasindex, relhasrules, "
"'t'::bool AS relhasoids, "
+ "'f'::bool AS relhasrowsecurity, "
"0 AS relfrozenxid, 0 AS relminmxid,"
"0 AS toid, "
"0 AS tfrozenxid, 0 AS tminmxid,"
*************** getTables(Archive *fout, int *numTables)
*** 4747,4752 ****
--- 4941,4947 ----
i_relhastriggers = PQfnumber(res, "relhastriggers");
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
+ i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
i_relhasoids = PQfnumber(res, "relhasoids");
i_relfrozenxid = PQfnumber(res, "relfrozenxid");
i_relminmxid = PQfnumber(res, "relminmxid");
*************** getTables(Archive *fout, int *numTables)
*** 4798,4803 ****
--- 4993,4999 ----
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].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));
*************** dumpDumpableObject(Archive *fout, Dumpab
*** 7923,7928 ****
--- 8119,8127 ----
NULL, 0,
dumpBlobs, NULL);
break;
+ case DO_ROW_SECURITY:
+ dumpRowSecurity(fout, (RowSecurityInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
*************** addBoundaryDependencies(DumpableObject *
*** 15325,15330 ****
--- 15524,15530 ----
case DO_TRIGGER:
case DO_EVENT_TRIGGER:
case DO_DEFAULT_ACL:
+ case DO_ROW_SECURITY:
/* Post-data objects: must come after the post-data boundary */
addObjectDependency(dobj, postDataBound->dumpId);
break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
new file mode 100644
index d184187..80f9f0c
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
*************** typedef enum
*** 111,117 ****
DO_PRE_DATA_BOUNDARY,
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
! DO_REFRESH_MATVIEW
} DumpableObjectType;
typedef struct _dumpableObject
--- 111,118 ----
DO_PRE_DATA_BOUNDARY,
DO_POST_DATA_BOUNDARY,
DO_EVENT_TRIGGER,
! DO_REFRESH_MATVIEW,
! DO_ROW_SECURITY
} DumpableObjectType;
typedef struct _dumpableObject
*************** typedef struct _tableInfo
*** 245,250 ****
--- 246,252 ----
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 hasoids; /* does it have OIDs? */
uint32 frozenxid; /* for restore frozen xid */
uint32 minmxid; /* for restore min multi xid */
*************** typedef struct _blobInfo
*** 486,491 ****
--- 488,502 ----
char *blobacl;
} BlobInfo;
+ typedef struct _rowSecurityInfo
+ {
+ DumpableObject dobj;
+ TableInfo *rstable;
+ char *rsecpolname;
+ char *rseccmd;
+ char *rsecqual;
+ } RowSecurityInfo;
+
/* global decls */
extern bool force_quotes; /* double-quotes for identifiers flag */
extern bool g_verbose; /* verbose flag */
*************** extern DefaultACLInfo *getDefaultACLs(Ar
*** 577,581 ****
--- 588,593 ----
extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers);
+ extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables);
#endif /* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
new file mode 100644
index f0caa6b..c0dcae6
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** describeDumpableObject(DumpableObject *o
*** 1434,1439 ****
--- 1434,1444 ----
"BLOB DATA (ID %d)",
obj->dumpId);
return;
+ case DO_ROW_SECURITY:
+ snprintf(buf, bufsize,
+ "ROW-SECURITY POLICY (ID %d OID %u)",
+ obj->dumpId, obj->catId.oid);
+ return;
case DO_PRE_DATA_BOUNDARY:
snprintf(buf, bufsize,
"PRE-DATA BOUNDARY (ID %d)",
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
new file mode 100644
index 4050091..304da8e
*** a/src/bin/pg_dump/pg_dumpall.c
--- b/src/bin/pg_dump/pg_dumpall.c
*************** dumpRoles(PGconn *conn)
*** 663,679 ****
i_rolpassword,
i_rolvaliduntil,
i_rolreplication,
i_rolcomment,
i_is_current_user;
int i;
/* note: rolconfig is dumped later */
! if (server_version >= 90100)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, "
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
--- 663,691 ----
i_rolpassword,
i_rolvaliduntil,
i_rolreplication,
+ i_rolbypassrls,
i_rolcomment,
i_is_current_user;
int i;
/* note: rolconfig is dumped later */
! if (server_version >= 90500)
! printfPQExpBuffer(buf,
! "SELECT oid, rolname, rolsuper, rolinherit, "
! "rolcreaterole, rolcreatedb, "
! "rolcanlogin, rolconnlimit, rolpassword, "
! "rolvaliduntil, rolreplication, rolbypassrls"
! "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
! "rolname = current_user AS is_current_user "
! "FROM pg_authid "
! "ORDER BY 2");
! else if (server_version >= 90100)
printfPQExpBuffer(buf,
"SELECT oid, rolname, rolsuper, rolinherit, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, "
+ "rolsuper as rolbypassrls"
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
*************** dumpRoles(PGconn *conn)
*** 684,689 ****
--- 696,702 ----
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, false as rolreplication, "
+ "rolsuper as rolbypassrls"
"pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
*************** dumpRoles(PGconn *conn)
*** 694,699 ****
--- 707,713 ----
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, false as rolreplication, "
+ "rolsuper as rolbypassrls"
"null as rolcomment, "
"rolname = current_user AS is_current_user "
"FROM pg_authid "
*************** dumpRoles(PGconn *conn)
*** 724,729 ****
--- 738,744 ----
"null::text as rolpassword, "
"null::abstime as rolvaliduntil, "
"false as rolreplication, "
+ "rolsuper as rolbypassrls, "
"null as rolcomment, false "
"FROM pg_group "
"WHERE NOT EXISTS (SELECT 1 FROM pg_shadow "
*************** dumpRoles(PGconn *conn)
*** 743,748 ****
--- 758,764 ----
i_rolpassword = PQfnumber(res, "rolpassword");
i_rolvaliduntil = PQfnumber(res, "rolvaliduntil");
i_rolreplication = PQfnumber(res, "rolreplication");
+ i_rolbypassrls = PQfnumber(res, "rolbypassrls");
i_rolcomment = PQfnumber(res, "rolcomment");
i_is_current_user = PQfnumber(res, "is_current_user");
*************** dumpRoles(PGconn *conn)
*** 810,815 ****
--- 826,836 ----
else
appendPQExpBufferStr(buf, " NOREPLICATION");
+ if (strcmp(PQgetvalue(res, i, i_rolbypassrls), "t") == 0)
+ appendPQExpBufferStr(buf, " BYPASSRLS");
+ else
+ appendPQExpBufferStr(buf, " NOBYPASSRLS");
+
if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0)
appendPQExpBuffer(buf, " CONNECTION LIMIT %s",
PQgetvalue(res, i, i_rolconnlimit));
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
new file mode 100644
index 282cd43..7fad59c
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** describeRoles(const char *pattern, bool
*** 2529,2534 ****
--- 2529,2539 ----
appendPQExpBufferStr(&buf, "\n, r.rolreplication");
}
+ if (pset.sversion >= 90500)
+ {
+ appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
+ }
+
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
processSQLNamePattern(pset.db, &buf, pattern, false, false,
*************** listTables(const char *tabtypes, const c
*** 2796,2801 ****
--- 2801,2811 ----
appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
gettext_noop("Description"));
+
+ if (pset.sversion >= 90500)
+ appendPQExpBuffer(&buf,
+ ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+ gettext_noop("Row-security"));
}
appendPQExpBufferStr(&buf,
*************** listTables(const char *tabtypes, const c
*** 2805,2810 ****
--- 2815,2823 ----
appendPQExpBufferStr(&buf,
"\n LEFT JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid"
"\n LEFT JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid");
+ if (verbose && pset.sversion >= 90500)
+ appendPQExpBuffer(&buf,
+ "\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
if (showTables)
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
new file mode 100644
index 8ed2592..6a4913a
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
*************** typedef enum ObjectClass
*** 147,152 ****
--- 147,153 ----
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
+ OCLASS_ROWSECURITY, /* pg_rowsecurity */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
new file mode 100644
index 59576fd..1120380
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 299,304 ****
--- 299,310 ----
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
+ DECLARE_UNIQUE_INDEX(pg_rowsecurity_oid_index, 5001, on pg_rowsecurity using btree(oid oid_ops));
+ #define RowSecurityOidIndexId 5001
+
+ DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 5002, on pg_rowsecurity using btree(rsecpolname name_ops, rsecrelid oid_ops, rseccmd char_ops));
+ #define RowSecurityRelidIndexId 5002
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
new file mode 100644
index e7c32c9..3b63d2b
*** a/src/include/catalog/pg_authid.h
--- b/src/include/catalog/pg_authid.h
*************** CATALOG(pg_authid,1260) BKI_SHARED_RELAT
*** 52,57 ****
--- 52,58 ----
bool rolcatupdate; /* allowed to alter catalogs manually? */
bool rolcanlogin; /* allowed to log in as session user? */
bool rolreplication; /* role used for streaming replication */
+ bool rolbypassrls; /* allowed to bypass row level security? */
int32 rolconnlimit; /* max connections allowed (-1=no limit) */
/* remaining fields may be null; use heap_getattr to read them! */
*************** typedef FormData_pg_authid *Form_pg_auth
*** 73,79 ****
* compiler constants for pg_authid
* ----------------
*/
! #define Natts_pg_authid 11
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
--- 74,80 ----
* compiler constants for pg_authid
* ----------------
*/
! #define Natts_pg_authid 12
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolinherit 3
*************** typedef FormData_pg_authid *Form_pg_auth
*** 82,90 ****
#define Anum_pg_authid_rolcatupdate 6
#define Anum_pg_authid_rolcanlogin 7
#define Anum_pg_authid_rolreplication 8
! #define Anum_pg_authid_rolconnlimit 9
! #define Anum_pg_authid_rolpassword 10
! #define Anum_pg_authid_rolvaliduntil 11
/* ----------------
* initial contents of pg_authid
--- 83,92 ----
#define Anum_pg_authid_rolcatupdate 6
#define Anum_pg_authid_rolcanlogin 7
#define Anum_pg_authid_rolreplication 8
! #define Anum_pg_authid_rolbypassrls 9
! #define Anum_pg_authid_rolconnlimit 10
! #define Anum_pg_authid_rolpassword 11
! #define Anum_pg_authid_rolvaliduntil 12
/* ----------------
* initial contents of pg_authid
*************** typedef FormData_pg_authid *Form_pg_auth
*** 93,99 ****
* user choices.
* ----------------
*/
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t -1 _null_ _null_ ));
#define BOOTSTRAP_SUPERUSERID 10
--- 95,101 ----
* user choices.
* ----------------
*/
! DATA(insert OID = 10 ( "POSTGRES" t t t t t t t t -1 _null_ _null_));
#define BOOTSTRAP_SUPERUSERID 10
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
new file mode 100644
index f2fb317..f635351
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
*************** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI
*** 65,70 ****
--- 65,71 ----
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 relispopulated; /* matview currently holds query results */
char relreplident; /* see REPLICA_IDENTITY_xxx constants */
TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
*************** typedef FormData_pg_class *Form_pg_class
*** 94,100 ****
* ----------------
*/
! #define Natts_pg_class 29
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
--- 95,101 ----
* ----------------
*/
! #define Natts_pg_class 30
#define Anum_pg_class_relname 1
#define Anum_pg_class_relnamespace 2
#define Anum_pg_class_reltype 3
*************** typedef FormData_pg_class *Form_pg_class
*** 118,129 ****
#define Anum_pg_class_relhasrules 21
#define Anum_pg_class_relhastriggers 22
#define Anum_pg_class_relhassubclass 23
! #define Anum_pg_class_relispopulated 24
! #define Anum_pg_class_relreplident 25
! #define Anum_pg_class_relfrozenxid 26
! #define Anum_pg_class_relminmxid 27
! #define Anum_pg_class_relacl 28
! #define Anum_pg_class_reloptions 29
/* ----------------
* initial contents of pg_class
--- 119,131 ----
#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_relispopulated 25
! #define Anum_pg_class_relreplident 26
! #define Anum_pg_class_relfrozenxid 27
! #define Anum_pg_class_relminmxid 28
! #define Anum_pg_class_relacl 29
! #define Anum_pg_class_reloptions 30
/* ----------------
* initial contents of pg_class
*************** typedef FormData_pg_class *Form_pg_class
*** 138,150 ****
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
! DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f t n 3 1 _null_ _null_ ));
DESCR("");
--- 140,152 ----
* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId;
* similarly, "1" in relminmxid stands for FirstMultiXactId
*/
! DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
! DATA(insert OID = 1259 ( pg_class PGNSP 83 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
DESCR("");
diff --git a/src/include/catalog/pg_rowsecurity.h b/src/include/catalog/pg_rowsecurity.h
new file mode 100644
index ...66f993c
*** a/src/include/catalog/pg_rowsecurity.h
--- b/src/include/catalog/pg_rowsecurity.h
***************
*** 0 ****
--- 1,91 ----
+ /*
+ * pg_rowsecurity.h
+ * definition of the system catalog for row-security policy (pg_rowsecurity)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+ #ifndef PG_ROWSECURITY_H
+ #define PG_ROWSECURITY_H
+
+ #include "catalog/genbki.h"
+ #include "nodes/primnodes.h"
+ #include "utils/memutils.h"
+ #include "utils/relcache.h"
+
+ /* ----------------
+ * pg_rowsecurity definition. cpp turns this into
+ * typedef struct FormData_pg_rowsecurity
+ * ----------------
+ */
+ #define RowSecurityRelationId 5000
+
+ CATALOG(pg_rowsecurity,5000)
+ {
+ NameData rsecpolname; /* Policy name. */
+ Oid rsecrelid; /* Oid of the relation with policy. */
+ char rseccmd; /* One of ROWSECURITY_CMD_* below */
+
+ #ifdef CATALOG_VARLEN
+ pg_node_tree rsecqual; /* Policy quals. */
+ #endif
+ } FormData_pg_rowsecurity;
+
+ /* ----------------
+ * Form_pg_rowsecurity corresponds to a pointer to a row with
+ * the format of pg_rowsecurity relation.
+ * ----------------
+ */
+ typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+ /* ----------------
+ * compiler constants for pg_rowsecurity
+ * ----------------
+ */
+ #define Natts_pg_rowsecurity 4
+ #define Anum_pg_rowsecurity_rsecpolname 1
+ #define Anum_pg_rowsecurity_rsecrelid 2
+ #define Anum_pg_rowsecurity_rseccmd 3
+ #define Anum_pg_rowsecurity_rsecqual 4
+
+ #define ROWSECURITY_CMD_ALL 'a'
+ #define ROWSECURITY_CMD_SELECT 's'
+ #define ROWSECURITY_CMD_INSERT 'i'
+ #define ROWSECURITY_CMD_UPDATE 'u'
+ #define ROWSECURITY_CMD_DELETE 'd'
+
+ typedef struct
+ {
+ Oid rsecid;
+ Expr *qual;
+ bool hassublinks;
+ } RowSecurityEntry;
+
+ typedef struct
+ {
+ char *policy_name;
+ RowSecurityEntry *rsall; /* row-security policy for ALL */
+ RowSecurityEntry *rsselect; /* row-security policy for SELECT */
+ RowSecurityEntry *rsinsert; /* row-security policy for INSERT */
+ RowSecurityEntry *rsupdate; /* row-security policy for UPDATE */
+ RowSecurityEntry *rsdelete; /* row-security policy for DELETE */
+ } RowSecurityPolicy;
+
+ typedef struct
+ {
+ MemoryContext rscxt; /* row-security memory context */
+ List *policies; /* list of row-security policies */
+ } RowSecurityDesc;
+
+ /* GUC variable */
+ extern int row_security;
+
+ /* Possible values for row_security GUC */
+ typedef enum
+ {
+ ROW_SECURITY_OFF,
+ ROW_SECURITY_ON
+ } RowSecurityType;
+
+ #endif /* PG_ROWSECURITY_H */
diff --git a/src/include/commands/policy.h b/src/include/commands/policy.h
new file mode 100644
index ...692d608
*** a/src/include/commands/policy.h
--- b/src/include/commands/policy.h
***************
*** 0 ****
--- 1,28 ----
+ /*-------------------------------------------------------------------------
+ *
+ * policy.h
+ * prototypes for policy.c.
+ *
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/policy.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #ifndef POLICY_H
+ #define POLICY_H
+
+ #include "nodes/parsenodes.h"
+
+ extern void RelationBuildRowSecurity(Relation relation);
+
+ extern void RemovePolicyById(Oid policy_id);
+
+ extern Oid CreatePolicy(CreatePolicyStmt *stmt);
+ extern Oid AlterPolicy(AlterPolicyStmt *stmt);
+ extern void DropPolicy(DropPolicyStmt *stmt);
+
+ #endif /* POLICY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
new file mode 100644
index c2b786e..802c7f9
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
*************** extern int trace_recovery(int trace_leve
*** 272,277 ****
--- 272,278 ----
/* flags to be OR'd to form sec_context */
#define SECURITY_LOCAL_USERID_CHANGE 0x0001
#define SECURITY_RESTRICTED_OPERATION 0x0002
+ #define SECURITY_ROW_LEVEL_DISABLED 0x0004
extern char *DatabasePath;
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
new file mode 100644
index 067c768..85ac489
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 366,371 ****
--- 366,374 ----
T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
T_AlterSystemStmt,
+ T_CreatePolicyStmt,
+ T_AlterPolicyStmt,
+ T_DropPolicyStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 8364bef..53fa13b
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct ImportForeignSchemaStmt
*** 1854,1859 ****
--- 1854,1898 ----
List *options; /* list of options to pass to FDW */
} ImportForeignSchemaStmt;
+ /*----------------------
+ * Create POLICY Statement
+ *----------------------
+ */
+ typedef struct CreatePolicyStmt
+ {
+ NodeTag type;
+ char *policy_name; /* Policy's name */
+ RangeVar *table; /* the table name the policy applies to */
+ char *cmd; /* the command name the policy applies to */
+ Node *qual; /* the policy's condition */
+ } CreatePolicyStmt;
+
+ /*----------------------
+ * Alter POLICY Statement
+ *----------------------
+ */
+ typedef struct AlterPolicyStmt
+ {
+ NodeTag type;
+ char *policy_name; /* Policy's name */
+ RangeVar *table; /* the table name the policy applies to */
+ char *cmd; /* the command name the policy applies to */
+ Node *qual; /* the policy's condition */
+ } AlterPolicyStmt;
+
+ /*----------------------
+ * Drop POLICY Statement
+ *----------------------
+ */
+ typedef struct DropPolicyStmt
+ {
+ NodeTag type;
+ char *policy_name; /* Policy's name */
+ RangeVar *table; /* the table name the policy applies to */
+ char *cmd; /* the command name the policy applies to */
+ bool missing_ok; /* skip error if policy is missing. */
+ } DropPolicyStmt;
+
/* ----------------------
* Create TRIGGER Statement
* ----------------------
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
new file mode 100644
index b52e507..4ecd763
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
*************** PG_KEYWORD("passing", PASSING, UNRESERVE
*** 283,288 ****
--- 283,289 ----
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
+ PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
new file mode 100644
index b32ddf7..c6c53b7
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
*************** typedef enum ParseExprKind
*** 63,69 ****
EXPR_KIND_INDEX_PREDICATE, /* index predicate */
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
! EXPR_KIND_TRIGGER_WHEN /* WHEN condition in CREATE TRIGGER */
} ParseExprKind;
--- 63,70 ----
EXPR_KIND_INDEX_PREDICATE, /* index predicate */
EXPR_KIND_ALTER_COL_TRANSFORM, /* transform expr in ALTER COLUMN TYPE */
EXPR_KIND_EXECUTE_PARAMETER, /* parameter value in EXECUTE */
! EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
! EXPR_KIND_ROW_SECURITY /* ROW SECURITY policy for a table */
} ParseExprKind;
diff --git a/src/include/rewrite/rowsecurity.h b/src/include/rewrite/rowsecurity.h
new file mode 100644
index ...96d474f
*** a/src/include/rewrite/rowsecurity.h
--- b/src/include/rewrite/rowsecurity.h
***************
*** 0 ****
--- 1,31 ----
+ /* -------------------------------------------------------------------------
+ *
+ * rowsecurity.h
+ * prototypes for optimizer/rowsecurity.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+ #ifndef ROWSECURITY_H
+ #define ROWSECURITY_H
+
+ #include "nodes/execnodes.h"
+ #include "nodes/parsenodes.h"
+ #include "nodes/relation.h"
+ #include "utils/rel.h"
+
+ typedef List *(*row_security_policy_hook_type)(CmdType cmdtype,
+ Relation relation);
+
+ extern PGDLLIMPORT row_security_policy_hook_type row_security_policy_hook;
+
+ extern List *pull_row_security_policy(CmdType cmd, Relation relation);
+
+ extern bool prepend_row_security_quals(Query* root, RangeTblEntry* rte,
+ int rt_index);
+
+ extern bool is_rls_enabled(void);
+
+ #endif /* ROWSECURITY_H */
\ No newline at end of file
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
new file mode 100644
index 9430baa..45c1dc7
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
*************** extern bool pg_foreign_server_ownercheck
*** 326,330 ****
--- 326,331 ----
extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
extern bool has_createrole_privilege(Oid roleid);
+ extern bool has_bypassrls_privilege(Oid roleid);
#endif /* ACL_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 37b6cbb..8b96218
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 18,23 ****
--- 18,24 ----
#include "catalog/pg_am.h"
#include "catalog/pg_class.h"
#include "catalog/pg_index.h"
+ #include "catalog/pg_rowsecurity.h"
#include "fmgr.h"
#include "nodes/bitmapset.h"
#include "rewrite/prs2lock.h"
*************** typedef struct RelationData
*** 105,110 ****
--- 106,112 ----
RuleLock *rd_rules; /* rewrite rules */
MemoryContext rd_rulescxt; /* private memory cxt for rd_rules, if any */
TriggerDesc *trigdesc; /* Trigger info, or NULL if rel has none */
+ RowSecurityDesc *rsdesc; /* Row-security policy, or NULL */
/* data managed by RelationGetIndexList: */
List *rd_indexlist; /* list of OIDs of indexes on relation */
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index ...f419a75
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
***************
*** 0 ****
--- 1,1116 ----
+ --
+ -- Test of Row-level security feature
+ --
+ -- Clean up in case a prior regression run failed
+ -- Suppress NOTICE messages when users/groups don't exist
+ SET client_min_messages TO 'warning';
+ DROP USER IF EXISTS rls_regress_user0;
+ DROP USER IF EXISTS rls_regress_user1;
+ DROP USER IF EXISTS rls_regress_user2;
+ DROP USER IF EXISTS rls_regress_exempt_user;
+ DROP SCHEMA IF EXISTS rls_regress_schema CASCADE;
+ RESET client_min_messages;
+ -- initial setup
+ CREATE USER rls_regress_user0;
+ CREATE USER rls_regress_user1;
+ CREATE USER rls_regress_user2;
+ CREATE USER rls_regress_exempt_user BYPASSRLS;
+ CREATE SCHEMA rls_regress_schema;
+ GRANT ALL ON SCHEMA rls_regress_schema to public;
+ SET search_path = rls_regress_schema;
+ -- setup of malicious function
+ CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool
+ COST 0.0000001 LANGUAGE plpgsql
+ AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+ GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
+ -- BASIC Row-Level Security Scenario
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE TABLE uaccount (
+ pguser name primary key,
+ seclv int
+ );
+ GRANT SELECT ON uaccount TO public;
+ INSERT INTO uaccount VALUES
+ ('rls_regress_user0', 99),
+ ('rls_regress_user1', 1),
+ ('rls_regress_user2', 2),
+ ('rls_regress_user3', 3);
+ CREATE TABLE category (
+ cid int primary key,
+ cname text
+ );
+ GRANT ALL ON category TO public;
+ INSERT INTO category VALUES
+ (11, 'novel'),
+ (22, 'science fiction'),
+ (33, 'technology'),
+ (44, 'manga');
+ CREATE TABLE document (
+ did int primary key,
+ cid int references category(cid),
+ dlevel int not null,
+ dauthor name,
+ dtitle text
+ );
+ GRANT ALL ON document TO public;
+ INSERT INTO document VALUES
+ ( 1, 11, 1, 'rls_regress_user1', 'my first novel'),
+ ( 2, 11, 2, 'rls_regress_user1', 'my second novel'),
+ ( 3, 22, 2, 'rls_regress_user1', 'my science fiction'),
+ ( 4, 44, 1, 'rls_regress_user1', 'my first manga'),
+ ( 5, 44, 2, 'rls_regress_user1', 'my second manga'),
+ ( 6, 22, 1, 'rls_regress_user2', 'great science fiction'),
+ ( 7, 33, 2, 'rls_regress_user2', 'great technology book'),
+ ( 8, 44, 1, 'rls_regress_user2', 'great manga');
+ -- user's security level must be higher that or equal to document's
+ CREATE POLICY p1 ON document FOR ALL
+ USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+ -- viewpoint from rls_regress_user1
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great manga
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+-----------------------
+ 1 | 11 | 1 | rls_regress_user1 | my first novel
+ 4 | 44 | 1 | rls_regress_user1 | my first manga
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction
+ 8 | 44 | 1 | rls_regress_user2 | great manga
+ (4 rows)
+
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great manga
+ cid | did | dlevel | dauthor | dtitle | cname
+ -----+-----+--------+-------------------+-----------------------+-----------------
+ 11 | 1 | 1 | rls_regress_user1 | my first novel | novel
+ 44 | 4 | 1 | rls_regress_user1 | my first manga | manga
+ 22 | 6 | 1 | rls_regress_user2 | great science fiction | science fiction
+ 44 | 8 | 1 | rls_regress_user2 | great manga | manga
+ (4 rows)
+
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my second novel
+ NOTICE: f_leak => my science fiction
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => my second manga
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great technology book
+ NOTICE: f_leak => great manga
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+-----------------------
+ 1 | 11 | 1 | rls_regress_user1 | my first novel
+ 2 | 11 | 2 | rls_regress_user1 | my second novel
+ 3 | 22 | 2 | rls_regress_user1 | my science fiction
+ 4 | 44 | 1 | rls_regress_user1 | my first manga
+ 5 | 44 | 2 | rls_regress_user1 | my second manga
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction
+ 7 | 33 | 2 | rls_regress_user2 | great technology book
+ 8 | 44 | 1 | rls_regress_user2 | great manga
+ (8 rows)
+
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my second novel
+ NOTICE: f_leak => my science fiction
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => my second manga
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great technology book
+ NOTICE: f_leak => great manga
+ cid | did | dlevel | dauthor | dtitle | cname
+ -----+-----+--------+-------------------+-----------------------+-----------------
+ 11 | 1 | 1 | rls_regress_user1 | my first novel | novel
+ 11 | 2 | 2 | rls_regress_user1 | my second novel | novel
+ 22 | 3 | 2 | rls_regress_user1 | my science fiction | science fiction
+ 44 | 4 | 1 | rls_regress_user1 | my first manga | manga
+ 44 | 5 | 2 | rls_regress_user1 | my second manga | manga
+ 22 | 6 | 1 | rls_regress_user2 | great science fiction | science fiction
+ 33 | 7 | 2 | rls_regress_user2 | great technology book | technology
+ 44 | 8 | 1 | rls_regress_user2 | great manga | manga
+ (8 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+ ----------------------------------------------------------
+ Subquery Scan on document
+ Filter: f_leak(document.dtitle)
+ -> Seq Scan on document document_1
+ Filter: (dlevel <= $0)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = "current_user"())
+ (7 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+ ----------------------------------------------------------------------
+ Hash Join
+ Hash Cond: (category.cid = document.cid)
+ -> Seq Scan on category
+ -> Hash
+ -> Subquery Scan on document
+ Filter: f_leak(document.dtitle)
+ -> Seq Scan on document document_1
+ Filter: (dlevel <= $0)
+ InitPlan 1 (returns $0)
+ -> Index Scan using uaccount_pkey on uaccount
+ Index Cond: (pguser = "current_user"())
+ (11 rows)
+
+ -- only owner can change row-level security
+ ALTER POLICY p1 ON document FOR ALL USING (true); --fail
+ ERROR: must be owner of relation document
+ DROP POLICY p1 ON document FOR ALL; --fail
+ ERROR: must be owner of relation document
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER POLICY p1 ON document FOR ALL USING (dauthor = current_user);
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my second novel
+ NOTICE: f_leak => my science fiction
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => my second manga
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+--------------------
+ 1 | 11 | 1 | rls_regress_user1 | my first novel
+ 2 | 11 | 2 | rls_regress_user1 | my second novel
+ 3 | 22 | 2 | rls_regress_user1 | my science fiction
+ 4 | 44 | 1 | rls_regress_user1 | my first manga
+ 5 | 44 | 2 | rls_regress_user1 | my second manga
+ (5 rows)
+
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+ NOTICE: f_leak => my first novel
+ NOTICE: f_leak => my second novel
+ NOTICE: f_leak => my science fiction
+ NOTICE: f_leak => my first manga
+ NOTICE: f_leak => my second manga
+ cid | did | dlevel | dauthor | dtitle | cname
+ -----+-----+--------+-------------------+--------------------+-----------------
+ 11 | 1 | 1 | rls_regress_user1 | my first novel | novel
+ 11 | 2 | 2 | rls_regress_user1 | my second novel | novel
+ 22 | 3 | 2 | rls_regress_user1 | my science fiction | science fiction
+ 44 | 4 | 1 | rls_regress_user1 | my first manga | manga
+ 44 | 5 | 2 | rls_regress_user1 | my second manga | manga
+ (5 rows)
+
+ -- viewpoint from rls_regres_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great technology book
+ NOTICE: f_leak => great manga
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+-----------------------
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction
+ 7 | 33 | 2 | rls_regress_user2 | great technology book
+ 8 | 44 | 1 | rls_regress_user2 | great manga
+ (3 rows)
+
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+ NOTICE: f_leak => great science fiction
+ NOTICE: f_leak => great technology book
+ NOTICE: f_leak => great manga
+ cid | did | dlevel | dauthor | dtitle | cname
+ -----+-----+--------+-------------------+-----------------------+-----------------
+ 22 | 6 | 1 | rls_regress_user2 | great science fiction | science fiction
+ 33 | 7 | 2 | rls_regress_user2 | great technology book | technology
+ 44 | 8 | 1 | rls_regress_user2 | great manga | manga
+ (3 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ QUERY PLAN
+ ----------------------------------------------
+ Subquery Scan on document
+ Filter: f_leak(document.dtitle)
+ -> Seq Scan on document document_1
+ Filter: (dauthor = "current_user"())
+ (4 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ QUERY PLAN
+ ----------------------------------------------------
+ Nested Loop
+ -> Subquery Scan on document
+ Filter: f_leak(document.dtitle)
+ -> Seq Scan on document document_1
+ Filter: (dauthor = "current_user"())
+ -> Index Scan using category_pkey on category
+ Index Cond: (cid = document.cid)
+ (7 rows)
+
+ -- interaction of FK/PK constraints
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE POLICY p2 ON category FOR ALL
+ USING (CASE WHEN current_user = 'rls_regress_user1' THEN cid IN (11, 33)
+ WHEN current_user = 'rls_regress_user2' THEN cid IN (22, 44)
+ ELSE false END);
+ -- cannot delete PK referenced by invisible FK
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ did | cid | dlevel | dauthor | dtitle | cid | cname
+ -----+-----+--------+-------------------+--------------------+-----+------------
+ 2 | 11 | 2 | rls_regress_user1 | my second novel | 11 | novel
+ 1 | 11 | 1 | rls_regress_user1 | my first novel | 11 | novel
+ | | | | | 33 | technology
+ 5 | 44 | 2 | rls_regress_user1 | my second manga | |
+ 4 | 44 | 1 | rls_regress_user1 | my first manga | |
+ 3 | 22 | 2 | rls_regress_user1 | my science fiction | |
+ (6 rows)
+
+ DELETE FROM category WHERE cid = 33; -- fails with FK violation
+ ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
+ DETAIL: Key (cid)=(33) is still referenced from table "document".
+ -- cannot insert FK referencing invisible PK
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ did | cid | dlevel | dauthor | dtitle | cid | cname
+ -----+-----+--------+-------------------+-----------------------+-----+-----------------
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction | 22 | science fiction
+ 8 | 44 | 1 | rls_regress_user2 | great manga | 44 | manga
+ 7 | 33 | 2 | rls_regress_user2 | great technology book | |
+ (3 rows)
+
+ INSERT INTO document VALUES (10, 33, 1, current_user, 'hoge'); -- fail wil FK violation
+ ERROR: insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+ DETAIL: Key (cid)=(33) is not present in table "category".
+ -- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
+ ERROR: duplicate key value violates unique constraint "document_pkey"
+ DETAIL: Key (did)=(8) already exists.
+ SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+---------+--------
+ (0 rows)
+
+ -- database superuser cannot bypass RLS policy when enabled
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO ON;
+ SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+---------+--------
+ (0 rows)
+
+ SELECT * FROM category;
+ cid | cname
+ -----+-------
+ (0 rows)
+
+ -- database superuser can bypass RLS policy when disabled
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+-----------------------
+ 1 | 11 | 1 | rls_regress_user1 | my first novel
+ 2 | 11 | 2 | rls_regress_user1 | my second novel
+ 3 | 22 | 2 | rls_regress_user1 | my science fiction
+ 4 | 44 | 1 | rls_regress_user1 | my first manga
+ 5 | 44 | 2 | rls_regress_user1 | my second manga
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction
+ 7 | 33 | 2 | rls_regress_user2 | great technology book
+ 8 | 44 | 1 | rls_regress_user2 | great manga
+ (8 rows)
+
+ SELECT * FROM category;
+ cid | cname
+ -----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+ (4 rows)
+
+ -- database non-superuser with bypass privilege can bypass RLS policy when disabled
+ SET SESSION AUTHORIZATION rls_regress_exempt_user;
+ SET row_security TO OFF;
+ SELECT * FROM document;
+ did | cid | dlevel | dauthor | dtitle
+ -----+-----+--------+-------------------+-----------------------
+ 1 | 11 | 1 | rls_regress_user1 | my first novel
+ 2 | 11 | 2 | rls_regress_user1 | my second novel
+ 3 | 22 | 2 | rls_regress_user1 | my science fiction
+ 4 | 44 | 1 | rls_regress_user1 | my first manga
+ 5 | 44 | 2 | rls_regress_user1 | my second manga
+ 6 | 22 | 1 | rls_regress_user2 | great science fiction
+ 7 | 33 | 2 | rls_regress_user2 | great technology book
+ 8 | 44 | 1 | rls_regress_user2 | great manga
+ (8 rows)
+
+ SELECT * FROM category;
+ cid | cname
+ -----+-----------------
+ 11 | novel
+ 22 | science fiction
+ 33 | technology
+ 44 | manga
+ (4 rows)
+
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SET row_security TO ON;
+ CREATE TABLE t1 (a int, junk1 text, b text) WITH OIDS;
+ ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor
+ GRANT ALL ON t1 TO public;
+ COPY t1 FROM stdin WITH (oids);
+ CREATE TABLE t2 (c float) INHERITS (t1);
+ COPY t2 FROM stdin WITH (oids);
+ CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
+ ALTER TABLE t3 INHERIT t1;
+ COPY t3(a,b,c) FROM stdin WITH (oids);
+ CREATE POLICY p1 ON t1 FOR ALL USING (a % 2 = 0); -- be even number
+ CREATE POLICY p2 ON t2 FOR ALL USING (a % 2 = 1); -- be odd number
+ SELECT * FROM t1;
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1;
+ QUERY PLAN
+ -------------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (7 rows)
+
+ SELECT * FROM t1 WHERE f_leak(b);
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => bcd
+ NOTICE: f_leak => def
+ NOTICE: f_leak => yyy
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+ -------------------------------------
+ Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (9 rows)
+
+ -- reference to system column
+ SELECT oid, * FROM t1;
+ oid | a | b
+ -----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 202 | 2 | bcd
+ 204 | 4 | def
+ 302 | 2 | yyy
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+ QUERY PLAN
+ -------------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (7 rows)
+
+ -- reference to whole-row reference
+ SELECT *, t1 FROM t1;
+ a | b | t1
+ ---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 2 | bcd | (2,bcd)
+ 4 | def | (4,def)
+ 2 | yyy | (2,yyy)
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+ QUERY PLAN
+ -------------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (7 rows)
+
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
+ QUERY PLAN
+ -------------------------------------------------------
+ LockRows
+ -> Subquery Scan on t1
+ -> LockRows
+ -> Result
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (11 rows)
+
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => bcd
+ NOTICE: f_leak => def
+ NOTICE: f_leak => yyy
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ 2 | bcd
+ 4 | def
+ 2 | yyy
+ (5 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ QUERY PLAN
+ -------------------------------------------------------
+ LockRows
+ -> Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> LockRows
+ -> Result
+ -> Append
+ -> Seq Scan on t1 t1_1
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (12 rows)
+
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ NOTICE: f_leak => aaa
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ccc
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => abc
+ NOTICE: f_leak => bcd
+ NOTICE: f_leak => cde
+ NOTICE: f_leak => def
+ NOTICE: f_leak => xxx
+ NOTICE: f_leak => yyy
+ NOTICE: f_leak => zzz
+ a | b
+ ---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+ (11 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+ ---------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: f_leak(b)
+ -> Seq Scan on t2
+ Filter: f_leak(b)
+ -> Seq Scan on t3
+ Filter: f_leak(b)
+ (7 rows)
+
+ -- non-superuser with bypass privilege can bypass RLS policy when disabled
+ SET SESSION AUTHORIZATION rls_regress_exempt_user;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ NOTICE: f_leak => aaa
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ccc
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => abc
+ NOTICE: f_leak => bcd
+ NOTICE: f_leak => cde
+ NOTICE: f_leak => def
+ NOTICE: f_leak => xxx
+ NOTICE: f_leak => yyy
+ NOTICE: f_leak => zzz
+ a | b
+ ---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+ (11 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+ ---------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: f_leak(b)
+ -> Seq Scan on t2
+ Filter: f_leak(b)
+ -> Seq Scan on t3
+ Filter: f_leak(b)
+ (7 rows)
+
+ ----- Dependencies -----
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SET row_security TO ON;
+ CREATE TABLE dependee (x integer, y integer);
+ CREATE TABLE dependent (x integer, y integer);
+ CREATE POLICY d1 ON dependent FOR ALL USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
+ DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row-security qual?
+ ERROR: cannot drop table dependee because other objects depend on it
+ DETAIL: row-security of table dependent FOR ALL depends on table dependee
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ DROP TABLE dependee CASCADE;
+ NOTICE: drop cascades to row-security of table dependent FOR ALL
+ EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
+ QUERY PLAN
+ -----------------------
+ Seq Scan on dependent
+ (1 row)
+
+ ----- RECURSION ----
+ --
+ -- Simple recursion
+ --
+ CREATE TABLE rec1 (x integer, y integer);
+ CREATE POLICY r1 ON rec1 FOR ALL
+ USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+ SELECT * FROM rec1; -- fail, direct recursion
+ ERROR: infinite recursion detected in row-security policy for relation "rec1"
+ --
+ -- Mutual recursion
+ --
+ CREATE TABLE rec2 (a integer, b integer);
+ ALTER POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2 WHERE b = y));
+ CREATE POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1 WHERE y = b));
+ SELECT * FROM rec1; -- fail, mutual recursion
+ ERROR: infinite recursion detected in row-security policy for relation "rec1"
+ --
+ -- Mutual recursion via views
+ --
+ CREATE VIEW rec1v AS SELECT * FROM rec1;
+ CREATE VIEW rec2v AS SELECT * FROM rec2;
+ ALTER POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2v WHERE b = y));
+ ALTER POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1v WHERE y = b));
+ SELECT * FROM rec1; -- fail, mutual recursion via views
+ ERROR: infinite recursion detected in row-security policy for relation "rec1"
+ --
+ -- Mutual recursion via .s.b views
+ --
+ DROP VIEW rec1v, rec2v CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to row-security of table rec1 FOR ALL
+ drop cascades to row-security of table rec2 FOR ALL
+ CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
+ CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
+ CREATE POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2v WHERE b = y));
+ CREATE POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1v WHERE y = b));
+ SELECT * FROM rec1; -- fail, mutual recursion via s.b. views
+ ERROR: infinite recursion detected in row-security policy for relation "rec1"
+ --
+ -- recursive RLS and VIEWs in policy
+ --
+ CREATE TABLE s1 (a int, b text);
+ INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+ CREATE TABLE s2 (x int, y text);
+ INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
+ CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+ CREATE POLICY p1 ON s1 FOR ALL
+ USING (a in (select x from s2 where y like '%2f%'));
+ CREATE POLICY p2 ON s2 FOR ALL
+ USING (x in (select a from s1 where b like '%22%'));
+ SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+ ERROR: infinite recursion detected in row-security policy for relation "s1"
+ ALTER POLICY p2 ON s2 FOR ALL USING (x % 2 = 0);
+ SELECT * FROM s1 WHERE f_leak(b); -- OK
+ NOTICE: f_leak => c81e728d9d4c2f636f067f89cc14862c
+ NOTICE: f_leak => a87ff679a2f3e71d9181a67b7542122c
+ a | b
+ ---+----------------------------------
+ 2 | c81e728d9d4c2f636f067f89cc14862c
+ 4 | a87ff679a2f3e71d9181a67b7542122c
+ (2 rows)
+
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+ QUERY PLAN
+ ---------------------------------------------------------------------------------------
+ Subquery Scan on s1 (cost=28.55..61.67 rows=205 width=36)
+ Filter: f_leak(s1.b)
+ -> Hash Join (cost=28.55..55.52 rows=615 width=36)
+ Hash Cond: (s1_1.a = s2.x)
+ -> Seq Scan on s1 s1_1 (cost=0.00..22.30 rows=1230 width=36)
+ -> Hash (cost=28.54..28.54 rows=1 width=4)
+ -> HashAggregate (cost=28.53..28.54 rows=1 width=4)
+ Group Key: s2.x
+ -> Subquery Scan on s2 (cost=0.00..28.52 rows=1 width=4)
+ Filter: (s2.y ~~ '%2f%'::text)
+ -> Seq Scan on s2 s2_1 (cost=0.00..28.45 rows=6 width=36)
+ Filter: ((x % 2) = 0)
+ (12 rows)
+
+ ALTER POLICY p1 ON s1 FOR ALL
+ USING (a in (select x from v2)); -- using VIEW in RLS policy
+ SELECT * FROM s1 WHERE f_leak(b); -- OK
+ NOTICE: f_leak => 0267aaf632e87a63288a08331f22c7c3
+ NOTICE: f_leak => 1679091c5a880faf6fb5e6087eb1b2dc
+ a | b
+ ----+----------------------------------
+ -4 | 0267aaf632e87a63288a08331f22c7c3
+ 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (2 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+ QUERY PLAN
+ ----------------------------------------------------------
+ Subquery Scan on s1
+ Filter: f_leak(s1.b)
+ -> Hash Join
+ Hash Cond: (s1_1.a = s2.x)
+ -> Seq Scan on s1 s1_1
+ -> Hash
+ -> HashAggregate
+ Group Key: s2.x
+ -> Subquery Scan on s2
+ Filter: (s2.y ~~ '%af%'::text)
+ -> Seq Scan on s2 s2_1
+ Filter: ((x % 2) = 0)
+ (12 rows)
+
+ SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ xx | x | y
+ ----+----+----------------------------------
+ -6 | -6 | 596a3d04481816330f07e4f97510c28f
+ -4 | -4 | 0267aaf632e87a63288a08331f22c7c3
+ 2 | 2 | c81e728d9d4c2f636f067f89cc14862c
+ (3 rows)
+
+ EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ QUERY PLAN
+ --------------------------------------------------------------------
+ Subquery Scan on s2
+ Filter: (s2.y ~~ '%28%'::text)
+ -> Seq Scan on s2 s2_1
+ Filter: ((x % 2) = 0)
+ SubPlan 1
+ -> Limit
+ -> Subquery Scan on s1
+ -> Nested Loop Semi Join
+ Join Filter: (s1_1.a = s2_2.x)
+ -> Seq Scan on s1 s1_1
+ -> Materialize
+ -> Subquery Scan on s2_2
+ Filter: (s2_2.y ~~ '%af%'::text)
+ -> Seq Scan on s2 s2_3
+ Filter: ((x % 2) = 0)
+ (15 rows)
+
+ ALTER POLICY p2 ON s2 FOR ALL
+ USING (x in (select a from s1 where b like '%d2%'));
+ SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view)
+ ERROR: infinite recursion detected in row-security policy for relation "s1"
+ -- prepared statement with rls_regress_user0 privilege
+ PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+ EXECUTE p1(2);
+ a | b
+ ---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+ (3 rows)
+
+ EXPLAIN (COSTS OFF) EXECUTE p1(2);
+ QUERY PLAN
+ ----------------------------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3
+ Filter: ((a <= 2) AND ((a % 2) = 0))
+ (7 rows)
+
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ NOTICE: f_leak => aaa
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ccc
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => abc
+ NOTICE: f_leak => bcd
+ NOTICE: f_leak => cde
+ NOTICE: f_leak => def
+ NOTICE: f_leak => xxx
+ NOTICE: f_leak => yyy
+ NOTICE: f_leak => zzz
+ a | b
+ ---+-----
+ 1 | aaa
+ 2 | bbb
+ 3 | ccc
+ 4 | ddd
+ 1 | abc
+ 2 | bcd
+ 3 | cde
+ 4 | def
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+ (11 rows)
+
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+ ---------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: f_leak(b)
+ -> Seq Scan on t2
+ Filter: f_leak(b)
+ -> Seq Scan on t3
+ Filter: f_leak(b)
+ (7 rows)
+
+ -- plan cache should be invalidated
+ EXECUTE p1(2);
+ a | b
+ ---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+ (6 rows)
+
+ EXPLAIN (COSTS OFF) EXECUTE p1(2);
+ QUERY PLAN
+ --------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: (a <= 2)
+ -> Seq Scan on t2
+ Filter: (a <= 2)
+ -> Seq Scan on t3
+ Filter: (a <= 2)
+ (7 rows)
+
+ PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+ EXECUTE p2(2);
+ a | b
+ ---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+ (3 rows)
+
+ EXPLAIN (COSTS OFF) EXECUTE p2(2);
+ QUERY PLAN
+ -------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: (a = 2)
+ -> Seq Scan on t2
+ Filter: (a = 2)
+ -> Seq Scan on t3
+ Filter: (a = 2)
+ (7 rows)
+
+ -- also, case when privilege switch from superuser
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SET row_security TO ON;
+ EXECUTE p2(2);
+ a | b
+ ---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+ (3 rows)
+
+ EXPLAIN (COSTS OFF) EXECUTE p2(2);
+ QUERY PLAN
+ ---------------------------------------------
+ Append
+ -> Seq Scan on t1
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t2
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ -> Seq Scan on t3
+ Filter: ((a = 2) AND ((a % 2) = 0))
+ (7 rows)
+
+ --
+ -- UPDATE / DELETE and Row-level security
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
+ QUERY PLAN
+ -------------------------------------
+ Update on t1 t1_3
+ -> Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> Seq Scan on t1 t1_4
+ Filter: ((a % 2) = 0)
+ -> Subquery Scan on t1_1
+ Filter: f_leak(t1_1.b)
+ -> Seq Scan on t2
+ Filter: ((a % 2) = 0)
+ -> Subquery Scan on t1_2
+ Filter: f_leak(t1_2.b)
+ -> Seq Scan on t3
+ Filter: ((a % 2) = 0)
+ (13 rows)
+
+ UPDATE t1 SET b = b || b WHERE f_leak(b);
+ NOTICE: f_leak => bbb
+ NOTICE: f_leak => ddd
+ NOTICE: f_leak => abc
+ NOTICE: f_leak => cde
+ NOTICE: f_leak => xxx
+ NOTICE: f_leak => yyy
+ NOTICE: f_leak => zzz
+ EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+ QUERY PLAN
+ -------------------------------------
+ Update on t1 t1_1
+ -> Subquery Scan on t1
+ Filter: f_leak(t1.b)
+ -> Seq Scan on t1 t1_2
+ Filter: ((a % 2) = 0)
+ (5 rows)
+
+ UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+ NOTICE: f_leak => bbbbbb
+ NOTICE: f_leak => dddddd
+ -- returning clause with system column
+ UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+ NOTICE: f_leak => bbbbbb_updt
+ NOTICE: f_leak => dddddd_updt
+ oid | a | b | t1
+ -----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+ (2 rows)
+
+ UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
+ NOTICE: f_leak => bbbbbb_updt
+ NOTICE: f_leak => dddddd_updt
+ NOTICE: f_leak => abcabc
+ NOTICE: f_leak => cdecde
+ NOTICE: f_leak => xxxxxx
+ NOTICE: f_leak => yyyyyy
+ NOTICE: f_leak => zzzzzz
+ a | b
+ ---+-------------
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 1 | abcabc
+ 3 | cdecde
+ 1 | xxxxxx
+ 2 | yyyyyy
+ 3 | zzzzzz
+ (7 rows)
+
+ UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+ NOTICE: f_leak => bbbbbb_updt
+ NOTICE: f_leak => dddddd_updt
+ NOTICE: f_leak => abcabc
+ NOTICE: f_leak => cdecde
+ NOTICE: f_leak => xxxxxx
+ NOTICE: f_leak => yyyyyy
+ NOTICE: f_leak => zzzzzz
+ oid | a | b | t1
+ -----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+ 201 | 1 | abcabc | (1,abcabc)
+ 203 | 3 | cdecde | (3,cdecde)
+ 301 | 1 | xxxxxx | (1,xxxxxx)
+ 302 | 2 | yyyyyy | (2,yyyyyy)
+ 303 | 3 | zzzzzz | (3,zzzzzz)
+ (7 rows)
+
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM t1;
+ a | b
+ ---+-------------
+ 1 | aaa
+ 3 | ccc
+ 2 | bbbbbb_updt
+ 4 | dddddd_updt
+ 2 | bcd
+ 4 | def
+ 1 | abcabc
+ 3 | cdecde
+ 1 | xxxxxx
+ 2 | yyyyyy
+ 3 | zzzzzz
+ (11 rows)
+
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ EXPLAIN (costs off) DELETE FROM only t1 WHERE f_leak(b);
+ QUERY PLAN
+ -------------------------------------
+ Delete on t1
+ -> Subquery Scan on t1_1
+ Filter: f_leak(t1_1.b)
+ -> Seq Scan on t1 t1_2
+ Filter: ((a % 2) = 0)
+ (5 rows)
+
+ EXPLAIN (costs off) DELETE FROM t1 WHERE f_leak(b);
+ QUERY PLAN
+ -------------------------------------
+ Delete on t1
+ -> Subquery Scan on t1_1
+ Filter: f_leak(t1_1.b)
+ -> Seq Scan on t1 t1_2
+ Filter: ((a % 2) = 0)
+ -> Subquery Scan on t2
+ Filter: f_leak(t2.b)
+ -> Seq Scan on t2 t2_1
+ Filter: ((a % 2) = 1)
+ -> Seq Scan on t3
+ Filter: f_leak(b)
+ (11 rows)
+
+ DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
+ NOTICE: f_leak => bbbbbb_updt
+ NOTICE: f_leak => dddddd_updt
+ oid | a | b | t1
+ -----+---+-------------+-----------------
+ 102 | 2 | bbbbbb_updt | (2,bbbbbb_updt)
+ 104 | 4 | dddddd_updt | (4,dddddd_updt)
+ (2 rows)
+
+ DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
+ NOTICE: f_leak => abcabc
+ NOTICE: f_leak => cdecde
+ NOTICE: f_leak => xxxxxx
+ NOTICE: f_leak => yyyyyy
+ NOTICE: f_leak => zzzzzz
+ oid | a | b | t1
+ -----+---+--------+------------
+ 201 | 1 | abcabc | (1,abcabc)
+ 203 | 3 | cdecde | (3,cdecde)
+ 301 | 1 | xxxxxx | (1,xxxxxx)
+ 302 | 2 | yyyyyy | (2,yyyyyy)
+ 303 | 3 | zzzzzz | (3,zzzzzz)
+ (5 rows)
+
+ --
+ -- Test psql \dt+ command
+ --
+ DROP POLICY p2 ON category FOR ALL; -- too long qual
+ \dt+
+ List of relations
+ Schema | Name | Type | Owner | Size | Description | Row-security
+ --------------------+-----------+-------+-------------------+------------+-------------+----------------------------------
+ rls_regress_schema | category | table | rls_regress_user0 | 16 kB | |
+ rls_regress_schema | dependent | table | rls_regress_user0 | 0 bytes | |
+ rls_regress_schema | document | table | rls_regress_user0 | 16 kB | | (dauthor = "current_user"())
+ rls_regress_schema | rec1 | table | rls_regress_user0 | 0 bytes | | (x = ( SELECT rec2v.a +
+ | | | | | | FROM rec2v +
+ | | | | | | WHERE (rec2v.b = rec1.y)))
+ rls_regress_schema | rec2 | table | rls_regress_user0 | 0 bytes | | (a = ( SELECT rec1v.x +
+ | | | | | | FROM rec1v +
+ | | | | | | WHERE (rec1v.y = rec2.b)))
+ rls_regress_schema | s1 | table | rls_regress_user0 | 16 kB | | (a IN ( SELECT v2.x +
+ | | | | | | FROM v2))
+ rls_regress_schema | s2 | table | rls_regress_user0 | 16 kB | | (x IN ( SELECT s1.a +
+ | | | | | | FROM s1 +
+ | | | | | | WHERE (s1.b ~~ '%d2%'::text)))
+ rls_regress_schema | t1 | table | rls_regress_user0 | 16 kB | | ((a % 2) = 0)
+ rls_regress_schema | t2 | table | rls_regress_user0 | 16 kB | | ((a % 2) = 1)
+ rls_regress_schema | t3 | table | rls_regress_user0 | 16 kB | |
+ rls_regress_schema | uaccount | table | rls_regress_user0 | 8192 bytes | |
+ (11 rows)
+
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+ DROP SCHEMA rls_regress_schema CASCADE;
+ NOTICE: drop cascades to 15 other objects
+ DETAIL: drop cascades to function f_leak(text)
+ drop cascades to table uaccount
+ drop cascades to table category
+ drop cascades to table document
+ drop cascades to table t1
+ drop cascades to table t2
+ drop cascades to table t3
+ drop cascades to table dependent
+ drop cascades to table rec1
+ drop cascades to table rec2
+ drop cascades to view rec1v
+ drop cascades to view rec2v
+ drop cascades to table s1
+ drop cascades to table s2
+ drop cascades to view v2
+ DROP USER rls_regress_user0;
+ DROP USER rls_regress_user1;
+ DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index ca56b47..5c20fd6
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_roles| SELECT pg_authid.rolname,
*** 1389,1394 ****
--- 1389,1395 ----
pg_authid.rolconnlimit,
'********'::text AS rolpassword,
pg_authid.rolvaliduntil,
+ pg_authid.rolbypassrls,
s.setconfig AS rolconfig,
pg_authid.oid
FROM (pg_authid
*************** pg_tables| SELECT n.nspname AS schemanam
*** 2018,2024 ****
t.spcname AS tablespace,
c.relhasindex AS hasindexes,
c.relhasrules AS hasrules,
! c.relhastriggers AS hastriggers
FROM ((pg_class c
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
--- 2019,2026 ----
t.spcname AS tablespace,
c.relhasindex AS hasindexes,
c.relhasrules AS hasrules,
! c.relhastriggers AS hastriggers,
! c.relhasrowsecurity AS hasrowsecurity
FROM ((pg_class c
LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace)))
LEFT JOIN pg_tablespace t ON ((t.oid = c.reltablespace)))
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index 111d24c..2c8ec11
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
*************** pg_pltemplate|t
*** 121,126 ****
--- 121,127 ----
pg_proc|t
pg_range|t
pg_rewrite|t
+ pg_rowsecurity|t
pg_seclabel|t
pg_shdepend|t
pg_shdescription|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index c0416f4..ab6c4e2
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
*************** test: select_into select_distinct select
*** 83,89 ****
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate matview lock replica_identity
# ----------
# Another group of parallel tests
--- 83,89 ----
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate matview lock replica_identity rowsecurity
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ...9efcfea
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
***************
*** 0 ****
--- 1,382 ----
+ --
+ -- Test of Row-level security feature
+ --
+
+ -- Clean up in case a prior regression run failed
+
+ -- Suppress NOTICE messages when users/groups don't exist
+ SET client_min_messages TO 'warning';
+
+ DROP USER IF EXISTS rls_regress_user0;
+ DROP USER IF EXISTS rls_regress_user1;
+ DROP USER IF EXISTS rls_regress_user2;
+ DROP USER IF EXISTS rls_regress_exempt_user;
+
+ DROP SCHEMA IF EXISTS rls_regress_schema CASCADE;
+
+ RESET client_min_messages;
+
+ -- initial setup
+ CREATE USER rls_regress_user0;
+ CREATE USER rls_regress_user1;
+ CREATE USER rls_regress_user2;
+ CREATE USER rls_regress_exempt_user BYPASSRLS;
+
+ CREATE SCHEMA rls_regress_schema;
+ GRANT ALL ON SCHEMA rls_regress_schema to public;
+ SET search_path = rls_regress_schema;
+
+ -- setup of malicious function
+ CREATE OR REPLACE FUNCTION f_leak(text) RETURNS bool
+ COST 0.0000001 LANGUAGE plpgsql
+ AS 'BEGIN RAISE NOTICE ''f_leak => %'', $1; RETURN true; END';
+ GRANT EXECUTE ON FUNCTION f_leak(text) TO public;
+
+ -- BASIC Row-Level Security Scenario
+
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE TABLE uaccount (
+ pguser name primary key,
+ seclv int
+ );
+ GRANT SELECT ON uaccount TO public;
+ INSERT INTO uaccount VALUES
+ ('rls_regress_user0', 99),
+ ('rls_regress_user1', 1),
+ ('rls_regress_user2', 2),
+ ('rls_regress_user3', 3);
+
+ CREATE TABLE category (
+ cid int primary key,
+ cname text
+ );
+ GRANT ALL ON category TO public;
+ INSERT INTO category VALUES
+ (11, 'novel'),
+ (22, 'science fiction'),
+ (33, 'technology'),
+ (44, 'manga');
+
+ CREATE TABLE document (
+ did int primary key,
+ cid int references category(cid),
+ dlevel int not null,
+ dauthor name,
+ dtitle text
+ );
+ GRANT ALL ON document TO public;
+ INSERT INTO document VALUES
+ ( 1, 11, 1, 'rls_regress_user1', 'my first novel'),
+ ( 2, 11, 2, 'rls_regress_user1', 'my second novel'),
+ ( 3, 22, 2, 'rls_regress_user1', 'my science fiction'),
+ ( 4, 44, 1, 'rls_regress_user1', 'my first manga'),
+ ( 5, 44, 2, 'rls_regress_user1', 'my second manga'),
+ ( 6, 22, 1, 'rls_regress_user2', 'great science fiction'),
+ ( 7, 33, 2, 'rls_regress_user2', 'great technology book'),
+ ( 8, 44, 1, 'rls_regress_user2', 'great manga');
+
+ -- user's security level must be higher that or equal to document's
+ CREATE POLICY p1 ON document FOR ALL
+ USING (dlevel <= (SELECT seclv FROM uaccount WHERE pguser = current_user));
+
+ -- viewpoint from rls_regress_user1
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+ -- only owner can change row-level security
+ ALTER POLICY p1 ON document FOR ALL USING (true); --fail
+ DROP POLICY p1 ON document FOR ALL; --fail
+
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER POLICY p1 ON document FOR ALL USING (dauthor = current_user);
+
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+
+ -- viewpoint from rls_regres_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER by did;
+
+ EXPLAIN (COSTS OFF) SELECT * FROM document WHERE f_leak(dtitle);
+ EXPLAIN (COSTS OFF) SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+ -- interaction of FK/PK constraints
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ CREATE POLICY p2 ON category FOR ALL
+ USING (CASE WHEN current_user = 'rls_regress_user1' THEN cid IN (11, 33)
+ WHEN current_user = 'rls_regress_user2' THEN cid IN (22, 44)
+ ELSE false END);
+
+ -- cannot delete PK referenced by invisible FK
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ DELETE FROM category WHERE cid = 33; -- fails with FK violation
+
+ -- cannot insert FK referencing invisible PK
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
+ INSERT INTO document VALUES (10, 33, 1, current_user, 'hoge'); -- fail wil FK violation
+
+ -- UNIQUE or PRIMARY KEY constraint violation DOES reveal presence of row
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ INSERT INTO document VALUES (8, 44, 1, 'rls_regress_user1', 'my third manga'); -- Must fail with unique violation, revealing presence of did we can't see
+ SELECT * FROM document WHERE did = 8; -- and confirm we can't see it
+
+ -- database superuser cannot bypass RLS policy when enabled
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO ON;
+ SELECT * FROM document;
+ SELECT * FROM category;
+
+ -- database superuser can bypass RLS policy when disabled
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM document;
+ SELECT * FROM category;
+
+ -- database non-superuser with bypass privilege can bypass RLS policy when disabled
+ SET SESSION AUTHORIZATION rls_regress_exempt_user;
+ SET row_security TO OFF;
+ SELECT * FROM document;
+ SELECT * FROM category;
+
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+
+ SET row_security TO ON;
+
+ CREATE TABLE t1 (a int, b text) WITH OIDS;
+ -- ALTER TABLE t1 DROP COLUMN junk1; -- just a disturbing factor
+ GRANT ALL ON t1 TO public;
+
+ COPY t1 FROM stdin WITH (oids);
+ 101 1 aaa
+ 102 2 bbb
+ 103 3 ccc
+ 104 4 ddd
+ \.
+
+ CREATE TABLE t2 (c float) INHERITS (t1);
+ COPY t2 FROM stdin WITH (oids);
+ 201 1 abc 1.1
+ 202 2 bcd 2.2
+ 203 3 cde 3.3
+ 204 4 def 4.4
+ \.
+
+ CREATE TABLE t3 (c text, b text, a int) WITH OIDS;
+ ALTER TABLE t3 INHERIT t1;
+ COPY t3(a,b,c) FROM stdin WITH (oids);
+ 301 1 xxx X
+ 302 2 yyy Y
+ 303 3 zzz Z
+ \.
+
+ CREATE POLICY p1 ON t1 FOR ALL USING (a % 2 = 0); -- be even number
+ CREATE POLICY p2 ON t2 FOR ALL USING (a % 2 = 1); -- be odd number
+
+ SELECT * FROM t1;
+ EXPLAIN (COSTS OFF) SELECT * FROM t1;
+
+ SELECT * FROM t1 WHERE f_leak(b);
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+ -- reference to system column
+ SELECT oid, * FROM t1;
+ EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+
+ -- reference to whole-row reference
+ SELECT *, t1 FROM t1;
+ EXPLAIN (COSTS OFF) SELECT *, t1 FROM t1;
+
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 FOR SHARE;
+
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+ -- non-superuser with bypass privilege can bypass RLS policy when disabled
+ SET SESSION AUTHORIZATION rls_regress_exempt_user;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+ ----- Dependencies -----
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SET row_security TO ON;
+
+ CREATE TABLE dependee (x integer, y integer);
+
+ CREATE TABLE dependent (x integer, y integer);
+ CREATE POLICY d1 ON dependent FOR ALL USING (x = (SELECT d.x FROM dependee d WHERE d.y = y));
+
+ DROP TABLE dependee; -- Should fail without CASCADE due to dependency on row-security qual?
+
+ DROP TABLE dependee CASCADE;
+
+ EXPLAIN (COSTS OFF) SELECT * FROM dependent; -- After drop, should be unqualified
+
+ ----- RECURSION ----
+
+ --
+ -- Simple recursion
+ --
+ CREATE TABLE rec1 (x integer, y integer);
+ CREATE POLICY r1 ON rec1 FOR ALL
+ USING (x = (SELECT r.x FROM rec1 r WHERE y = r.y));
+
+ SELECT * FROM rec1; -- fail, direct recursion
+
+ --
+ -- Mutual recursion
+ --
+ CREATE TABLE rec2 (a integer, b integer);
+ ALTER POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2 WHERE b = y));
+ CREATE POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1 WHERE y = b));
+
+ SELECT * FROM rec1; -- fail, mutual recursion
+
+ --
+ -- Mutual recursion via views
+ --
+ CREATE VIEW rec1v AS SELECT * FROM rec1;
+ CREATE VIEW rec2v AS SELECT * FROM rec2;
+ ALTER POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2v WHERE b = y));
+ ALTER POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1v WHERE y = b));
+
+ SELECT * FROM rec1; -- fail, mutual recursion via views
+
+ --
+ -- Mutual recursion via .s.b views
+ --
+
+ DROP VIEW rec1v, rec2v CASCADE;
+ CREATE VIEW rec1v WITH (security_barrier) AS SELECT * FROM rec1;
+ CREATE VIEW rec2v WITH (security_barrier) AS SELECT * FROM rec2;
+ CREATE POLICY r1 ON rec1 FOR ALL USING (x = (SELECT a FROM rec2v WHERE b = y));
+ CREATE POLICY r2 ON rec2 FOR ALL USING (a = (SELECT x FROM rec1v WHERE y = b));
+
+ SELECT * FROM rec1; -- fail, mutual recursion via s.b. views
+
+ --
+ -- recursive RLS and VIEWs in policy
+ --
+ CREATE TABLE s1 (a int, b text);
+ INSERT INTO s1 (SELECT x, md5(x::text) FROM generate_series(-10,10) x);
+
+ CREATE TABLE s2 (x int, y text);
+ INSERT INTO s2 (SELECT x, md5(x::text) FROM generate_series(-6,6) x);
+ CREATE VIEW v2 AS SELECT * FROM s2 WHERE y like '%af%';
+
+ CREATE POLICY p1 ON s1 FOR ALL
+ USING (a in (select x from s2 where y like '%2f%'));
+
+ CREATE POLICY p2 ON s2 FOR ALL
+ USING (x in (select a from s1 where b like '%22%'));
+
+ SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion)
+
+ ALTER POLICY p2 ON s2 FOR ALL USING (x % 2 = 0);
+
+ SELECT * FROM s1 WHERE f_leak(b); -- OK
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ ALTER POLICY p1 ON s1 FOR ALL
+ USING (a in (select x from v2)); -- using VIEW in RLS policy
+ SELECT * FROM s1 WHERE f_leak(b); -- OK
+ EXPLAIN (COSTS OFF) SELECT * FROM s1 WHERE f_leak(b);
+
+ SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+ EXPLAIN (COSTS OFF) SELECT (SELECT x FROM s1 LIMIT 1) xx, * FROM s2 WHERE y like '%28%';
+
+ ALTER POLICY p2 ON s2 FOR ALL
+ USING (x in (select a from s1 where b like '%d2%'));
+ SELECT * FROM s1 WHERE f_leak(b); -- fail (infinite recursion via view)
+
+ -- prepared statement with rls_regress_user0 privilege
+ PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+ EXECUTE p1(2);
+ EXPLAIN (COSTS OFF) EXECUTE p1(2);
+
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ SET row_security TO OFF;
+ SELECT * FROM t1 WHERE f_leak(b);
+ EXPLAIN (COSTS OFF) SELECT * FROM t1 WHERE f_leak(b);
+
+ -- plan cache should be invalidated
+ EXECUTE p1(2);
+ EXPLAIN (COSTS OFF) EXECUTE p1(2);
+
+ PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+ EXECUTE p2(2);
+ EXPLAIN (COSTS OFF) EXECUTE p2(2);
+
+ -- also, case when privilege switch from superuser
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ SET row_security TO ON;
+ EXECUTE p2(2);
+ EXPLAIN (COSTS OFF) EXECUTE p2(2);
+
+ --
+ -- UPDATE / DELETE and Row-level security
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ EXPLAIN (COSTS OFF) UPDATE t1 SET b = b || b WHERE f_leak(b);
+ UPDATE t1 SET b = b || b WHERE f_leak(b);
+
+ EXPLAIN (COSTS OFF) UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+ UPDATE only t1 SET b = b || '_updt' WHERE f_leak(b);
+
+ -- returning clause with system column
+ UPDATE only t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+ UPDATE t1 SET b = b WHERE f_leak(b) RETURNING *;
+ UPDATE t1 SET b = b WHERE f_leak(b) RETURNING oid, *, t1;
+
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM t1;
+
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ EXPLAIN (COSTS OFF) DELETE FROM only t1 WHERE f_leak(b);
+ EXPLAIN (COSTS OFF) DELETE FROM t1 WHERE f_leak(b);
+
+ DELETE FROM only t1 WHERE f_leak(b) RETURNING oid, *, t1;
+ DELETE FROM t1 WHERE f_leak(b) RETURNING oid, *, t1;
+
+ --
+ -- Test psql \dt+ command
+ --
+ DROP POLICY p2 ON category FOR ALL; -- too long qual
+ \dt+
+
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+
+ DROP SCHEMA rls_regress_schema CASCADE;
+
+ DROP USER rls_regress_user0;
+ DROP USER rls_regress_user1;
+ DROP USER rls_regress_user2;