pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
Row-Level Security
165+ messages / 30 participants
[nested] [flat]

* Row-Level Security
@ 2009-12-12 20:30 Stephen Frost <sfrost@snowman.net>
  2009-12-12 23:30 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Stephen Frost @ 2009-12-12 20:30 UTC (permalink / raw)
  To: pgsql-hackers

Greetings,

> I'll start a new thread on this specific topic to hopefully pull out
> anyone who's focus is more on that than on SEPG.

Row-Level security has been implemented in a number of existing
commercial databases.  There exists an implementation of row-level
security for PostgreSQL today in the form of SEPostgres.
I believe there is a signfigant user base who would like RLS without
SELinux (or perhaps with some other security manager).  As it is a
useful feature indepenent of SELinux, it should be implemented in a way
which doesn't depend on SELinux in any way.

I've started a wiki page to discuss this here:
http://wiki.postgresql.org/wiki/RLS

I'd like to start a discussion about RLS for PG- design, user-interface,
syntax, capabilities, on-disk format changes, etc.  For starters, I
think we shoud review the existing RLS implementations.  To that end,
I've added a number of articles about them to the wiki.  I think the
next step is to start summarizing how those operate and important
similarities and differences between them.  Our goal, of course, is to
take the best of what's out there.

Please comment, update the wiki, let us know you're interested in this.. 

	Thanks!

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (196B, ../../20091212203003.GL17756@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
@ 2009-12-12 23:30 ` KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 00:11   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  1 sibling, 1 reply; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-12 23:30 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: pgsql-hackers

(2009/12/13 5:30), Stephen Frost wrote:
> Greetings,
> 
>> I'll start a new thread on this specific topic to hopefully pull out
>> anyone who's focus is more on that than on SEPG.
> 
> Row-Level security has been implemented in a number of existing
> commercial databases.  There exists an implementation of row-level
> security for PostgreSQL today in the form of SEPostgres.
> I believe there is a signfigant user base who would like RLS without
> SELinux (or perhaps with some other security manager).  As it is a
> useful feature indepenent of SELinux, it should be implemented in a way
> which doesn't depend on SELinux in any way.

Yes, it is also my plan.
If once PostgreSQL gets row-level granularity in access controls,
it is quite easy to add SELinux support as a security provider.


> I've started a wiki page to discuss this here:
> http://wiki.postgresql.org/wiki/RLS
> 
> I'd like to start a discussion about RLS for PG- design, user-interface,
> syntax, capabilities, on-disk format changes, etc.  For starters, I
> think we shoud review the existing RLS implementations.  To that end,
> I've added a number of articles about them to the wiki.  I think the
> next step is to start summarizing how those operate and important
> similarities and differences between them.  Our goal, of course, is to
> take the best of what's out there.
> 
> Please comment, update the wiki, let us know you're interested in this..

Good start, however, could you defer the discussion after the Feb-15?
My hands are now full in the security framework and SE-PgSQL/Lite. :(

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-12 23:30 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
@ 2009-12-13 00:11   ` Stephen Frost <sfrost@snowman.net>
  0 siblings, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2009-12-13 00:11 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@kaigai.gr.jp>; +Cc: pgsql-hackers

KaiGai,

* KaiGai Kohei (kaigai@kaigai.gr.jp) wrote:
> > Please comment, update the wiki, let us know you're interested in this..
> 
> Good start, however, could you defer the discussion after the Feb-15?
> My hands are now full in the security framework and SE-PgSQL/Lite. :(

While I'm glad you're enthusiastic and interested in this too, I don't
believe we need to delay this initial discussion.  To be honest, I think
we really need to get some input and interest from others as well.  I'll
do my best to make sure the wiki is updated with information and links
to any signifigant threads on the lists.  I don't expect to be writing
any serious code by Feb 15th on this anyway.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (196B, ../../20091213001113.GM17756@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
@ 2009-12-13 00:41 ` Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Josh Berkus @ 2009-12-13 00:41 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: pgsql-hackers

Stephen,

> Please comment, update the wiki, let us know you're interested in this.. 

I blogged about this some time ago.  One issue I can see is that I
believe that the RLS which many users want is different from the RLS
which SEPostgres implements.

Links:

http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-1-30732
http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-2-30757

--Josh Berkus



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
@ 2009-12-13 03:18   ` Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2009-12-13 03:18 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Stephen Frost <sfrost@snowman.net>; pgsql-hackers

On Sat, Dec 12, 2009 at 7:41 PM, Josh Berkus <josh@agliodbs.com> wrote:
> Stephen,
>
>> Please comment, update the wiki, let us know you're interested in this..
>
> I blogged about this some time ago.  One issue I can see is that I
> believe that the RLS which many users want is different from the RLS
> which SEPostgres implements.
>
> Links:
>
> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-1-30732
> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-2-30757
>
> --Josh Berkus

I read these blog entries a while ago but had forgotten about them.
They're very good, and summarize a lot of my thinking on this topic as
well.  I think that we can design a framework for row-level security
which can encompass both constraint-based security and label-based
security.  Both seem to me to be be based around doing essentially the
following things:

1. Adding columns to the table to store access control information.
2. Populating those columns with additional information (owner, ACL,
security label, etc.) which can be used to make access control
decisions.
3. Injecting logic into incoming queries which uses the information
inserted by (1) to filter out rows to which the access control policy
does not wish to allow access.

Waving my hands in the air, #1 and #2 seem pretty straightforward.
For constraint-based security, one can imagine just adding a column to
the table and then adding a BEFORE INSERT OR UPDATE FOR EACH ROW
trigger that populates that column.  For label-based MAC, that's not
going to be quite sufficient, because the system needs to ensure that
the trigger that populates the security column must run last; if it
doesn't, some other trigger can come along afterwards and slip in a
value that isn't supposed to be there; plus, it might be inconvenient
to need to define this trigger for every table that needs RLS.

However, those problems don't seem insurmountable.  Suppose we provide
a hook function that essentially acts like a global BEFORE INSERT OR
UPDATE trigger but which fires after all of the regular triggers.
SE-PostgreSQL can gain control at that point and search through the
columns of the target relation for a column called, say,
sepg_security_label.  If it finds such a column and that column is of
the appropriate type, then (1) if an explicit security label is
provided, it checks whether the specified label is permissible, (2)
otherwise, if the operation is insert, it determines the appropriate
default label for the current security context and inserts it, (3)
otherwise, it just leaves the current label alone.  This might not be
quite the right behavior but the point is whatever behavior you want
to have in terms of assigning/disallowing values for that column
should be possible to implement here.  The upshot is that if the
system administrator creates an sepg_security_label column of the
correct type, row-level security will be enabled for that table.
Otherwise, it will not.

#3 seems a little bit trickier.  I don't think the GRANT ... WHERE
syntax is going to be very easy to use.  For constraint-based
row-security, I think we should have something more like:

ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
WHERE where-clause

(This suffers from the same problem as DELETE ... USING, namely that
sometimes you want an outer join between table and othertable.)

This gives the user a convenient way to insert a join against one or
more side tables if they are so inclined.

For security frameworks like SE-PostgreSQL, we might just provide a
hook allowing the incoming query tree to be modified, and let the hook
function check whether each table in the query has row-level security
enabled, and if so perform a modification equivalent to the above.

None of this addresses the issue of doing RLS on system catalogs,
which seems like a much harder problem, possibly one that we should
just ignore for the first phase of this project.

Thoughts?

...Robert



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2009-12-13 08:50     ` KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-13 08:50 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

(2009/12/13 12:18), Robert Haas wrote:
> On Sat, Dec 12, 2009 at 7:41 PM, Josh Berkus<josh@agliodbs.com>  wrote:
>> Stephen,
>>
>>> Please comment, update the wiki, let us know you're interested in this..
>>
>> I blogged about this some time ago.  One issue I can see is that I
>> believe that the RLS which many users want is different from the RLS
>> which SEPostgres implements.
>>
>> Links:
>>
>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-1-30732
>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-2-30757
>>
>> --Josh Berkus
>
> I read these blog entries a while ago but had forgotten about them.
> They're very good, and summarize a lot of my thinking on this topic as
> well.  I think that we can design a framework for row-level security
> which can encompass both constraint-based security and label-based
> security.  Both seem to me to be be based around doing essentially the
> following things:
>
> 1. Adding columns to the table to store access control information.
> 2. Populating those columns with additional information (owner, ACL,
> security label, etc.) which can be used to make access control
> decisions.
> 3. Injecting logic into incoming queries which uses the information
> inserted by (1) to filter out rows to which the access control policy
> does not wish to allow access.
>
> Waving my hands in the air, #1 and #2 seem pretty straightforward.
> For constraint-based security, one can imagine just adding a column to
> the table and then adding a BEFORE INSERT OR UPDATE FOR EACH ROW
> trigger that populates that column.  For label-based MAC, that's not
> going to be quite sufficient, because the system needs to ensure that
> the trigger that populates the security column must run last; if it
> doesn't, some other trigger can come along afterwards and slip in a
> value that isn't supposed to be there; plus, it might be inconvenient
> to need to define this trigger for every table that needs RLS.

Right, label-based MAC need its hook being called after all the BR-Insert
triggers to assign a correct security label, not only access controls.
I'd like to point out one more thing. When we update tuples, "invisible"
tuples have to be filtered out before trigger functions.

> However, those problems don't seem insurmountable.  Suppose we provide
> a hook function that essentially acts like a global BEFORE INSERT OR
> UPDATE trigger but which fires after all of the regular triggers.

Basically, right. In my branch, SE-PgSQL put its hook after all the BR
trigger invocations.

http://code.google.com/p/sepgsql/source/browse/branches/pgsql-8.4.x/sepgsql/src/backend/executor/exe...

But we have another approach. When RelationBuildTriggers() initializes
TriggerDesc of Relation, we can inject security hook as a special BR-trigger
at the last. If we initialize it here, we don't need to modify COPY FROM
implementation, not only INSERT.

The reason why I didn't apply this approach is it needs more modification
to the core routines, so it makes harder to manage out-of-tree code.

> SE-PostgreSQL can gain control at that point and search through the
> columns of the target relation for a column called, say,
> sepg_security_label.  If it finds such a column and that column is of
> the appropriate type, then (1) if an explicit security label is
> provided, it checks whether the specified label is permissible, (2)
> otherwise, if the operation is insert, it determines the appropriate
> default label for the current security context and inserts it, (3)
> otherwise, it just leaves the current label alone.  This might not be
> quite the right behavior but the point is whatever behavior you want
> to have in terms of assigning/disallowing values for that column
> should be possible to implement here.  The upshot is that if the
> system administrator creates an sepg_security_label column of the
> correct type, row-level security will be enabled for that table.
> Otherwise, it will not.

Basically, right. SE-PgSQL (or others) assign a new tuple either an
explicitly given or a default security label, then it checks permission
whether the client can insert a tuple with this label, or not.

One point. MAC is "mandatory", so the table owner should not be able to
control whether row-level checks are applied, or not.
So, I used a special purpose system column to represent security label.
It is generated for each tables, and no additional storage consumption
when MAC feature is disabled.

> #3 seems a little bit trickier.  I don't think the GRANT ... WHERE
> syntax is going to be very easy to use.  For constraint-based
> row-security, I think we should have something more like:
>
> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
> WHERE where-clause
>
> (This suffers from the same problem as DELETE ... USING, namely that
> sometimes you want an outer join between table and othertable.)
>
> This gives the user a convenient way to insert a join against one or
> more side tables if they are so inclined.

Is it reasonably possible to implement USING clause, even if row-level
security is applied on COPY FROM/TO statement?

And, isn't it necessary to specify condition to apply the filter?
(such as select, update and delete)

> For security frameworks like SE-PostgreSQL, we might just provide a
> hook allowing the incoming query tree to be modified, and let the hook
> function check whether each table in the query has row-level security
> enabled, and if so perform a modification equivalent to the above.

One point we have to pay mention is all the row-level filter conditions
have to be evaluated before all the user given condition, except for
operators pulled-up to index accesses.
It allows malicious row-cost functions to leak "invisible" tuples anywhere.

> None of this addresses the issue of doing RLS on system catalogs,
> which seems like a much harder problem, possibly one that we should
> just ignore for the first phase of this project.

It is reasonable.

I'd like to point out a few more issues:

* TRUNCATE statement

Truncate is a good feature to clean up the contents of a table.
But it may contain unremovable tuples. So, it needs to scan a table to
be truncated once to confirm all the tuples can be removed by the current
user. It is a trade-off case between performance and security.

* Foreign Key constraint(1)

I don't think upcoming label-based MAC feature support covert channel issue.
Even if PK is invisible, we can guess PK exists from FK. In fact, commercial
database products (such as Oracle Label Security) also does not care about.

* Foreign Key constraint(2)

FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
If associated tuples are filtered out, it breaks reference integrity.
So, we have to apply special care. In SE-PgSQL case, it raises an error
instead of filtering during FK checks. And, row-level security hook is
called at the last for each tuples, unlike normal cases.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
@ 2009-12-13 12:11       ` Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2009-12-13 12:11 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@kaigai.gr.jp>; +Cc: Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

On Sun, Dec 13, 2009 at 3:50 AM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:
> (2009/12/13 12:18), Robert Haas wrote:
>> On Sat, Dec 12, 2009 at 7:41 PM, Josh Berkus<josh@agliodbs.com>  wrote:
>>> I blogged about this some time ago.  One issue I can see is that I
>>> believe that the RLS which many users want is different from the RLS
>>> which SEPostgres implements.
>>>
>>> Links:
>>>
>>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-1-30732
>>> http://it.toolbox.com/blogs/database-soup/thinking-about-row-level-security-part-2-30757
>>
>> I read these blog entries a while ago but had forgotten about them.
>> They're very good, and summarize a lot of my thinking on this topic as
>> well.  I think that we can design a framework for row-level security
>> which can encompass both constraint-based security and label-based
>> security.  Both seem to me to be be based around doing essentially the
>> following things:
>>
>> 1. Adding columns to the table to store access control information.
>> 2. Populating those columns with additional information (owner, ACL,
>> security label, etc.) which can be used to make access control
>> decisions.
>> 3. Injecting logic into incoming queries which uses the information
>> inserted by (1) to filter out rows to which the access control policy
>> does not wish to allow access.
>>
>> Waving my hands in the air, #1 and #2 seem pretty straightforward.
>> For constraint-based security, one can imagine just adding a column to
>> the table and then adding a BEFORE INSERT OR UPDATE FOR EACH ROW
>> trigger that populates that column.  For label-based MAC, that's not
>> going to be quite sufficient, because the system needs to ensure that
>> the trigger that populates the security column must run last; if it
>> doesn't, some other trigger can come along afterwards and slip in a
>> value that isn't supposed to be there; plus, it might be inconvenient
>> to need to define this trigger for every table that needs RLS.
>
> Right, label-based MAC need its hook being called after all the BR-Insert
> triggers to assign a correct security label, not only access controls.
> I'd like to point out one more thing. When we update tuples, "invisible"
> tuples have to be filtered out before trigger functions.
>
>> However, those problems don't seem insurmountable.  Suppose we provide
>> a hook function that essentially acts like a global BEFORE INSERT OR
>> UPDATE trigger but which fires after all of the regular triggers.
>
> Basically, right. In my branch, SE-PgSQL put its hook after all the BR
> trigger invocations.
>
> http://code.google.com/p/sepgsql/source/browse/branches/pgsql-8.4.x/sepgsql/src/backend/executor/exe...
>
> But we have another approach. When RelationBuildTriggers() initializes
> TriggerDesc of Relation, we can inject security hook as a special BR-trigger
> at the last. If we initialize it here, we don't need to modify COPY FROM
> implementation, not only INSERT.
>
> The reason why I didn't apply this approach is it needs more modification
> to the core routines, so it makes harder to manage out-of-tree code.

That's definitely something to consider if it's true.  Why did it
require more modification of the core routines?

>> SE-PostgreSQL can gain control at that point and search through the
>> columns of the target relation for a column called, say,
>> sepg_security_label.  If it finds such a column and that column is of
>> the appropriate type, then (1) if an explicit security label is
>> provided, it checks whether the specified label is permissible, (2)
>> otherwise, if the operation is insert, it determines the appropriate
>> default label for the current security context and inserts it, (3)
>> otherwise, it just leaves the current label alone.  This might not be
>> quite the right behavior but the point is whatever behavior you want
>> to have in terms of assigning/disallowing values for that column
>> should be possible to implement here.  The upshot is that if the
>> system administrator creates an sepg_security_label column of the
>> correct type, row-level security will be enabled for that table.
>> Otherwise, it will not.
>
> Basically, right. SE-PgSQL (or others) assign a new tuple either an
> explicitly given or a default security label, then it checks permission
> whether the client can insert a tuple with this label, or not.
>
> One point. MAC is "mandatory", so the table owner should not be able to
> control whether row-level checks are applied, or not.
> So, I used a special purpose system column to represent security label.
> It is generated for each tables, and no additional storage consumption
> when MAC feature is disabled.

My current feeling is that a special-purpose system column is not the
best approach.  I don't see what we gain by doing it that way.  Even
in an SE-PostgreSQL environment, row-level security might not be
desired on every table - after all, we've been told that SE-PostgreSQL
is useful without any row-level security AT ALL, so it's not hard to
think there could be environments where only some tables need to
protected.  So I think we want to have a way to turn it on and off on
a per-table basis.

Of course, as you point out, we have to make sure that anyone who
tries to turn RLS on or off for a particular table is authorized to
perform that operation.  But that's a separate problem which is I
don't think has much to do with row-level security.

>> #3 seems a little bit trickier.  I don't think the GRANT ... WHERE
>> syntax is going to be very easy to use.  For constraint-based
>> row-security, I think we should have something more like:
>>
>> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
>> WHERE where-clause
>>
>> (This suffers from the same problem as DELETE ... USING, namely that
>> sometimes you want an outer join between table and othertable.)
>>
>> This gives the user a convenient way to insert a join against one or
>> more side tables if they are so inclined.
>
> Is it reasonably possible to implement USING clause, even if row-level
> security is applied on COPY FROM/TO statement?
> And, isn't it necessary to specify condition to apply the filter?
> (such as select, update and delete)

The filter is the WHERE clause.  I would think that the operation
being performed (select, update, delete) wouldn't enter into it.  This
part is just to decide which tuples will actually be accessible AT
ALL.  If you want to further prevent certain tuples that are being
accessed from being update or deleted, you can use a trigger for that
(possibly one of the global, always-applied-last triggers discussed
above).

For INSERT and COPY, I don't think that the ALTER TABLE ... ADD ROW
FILTER stuff would apply.  If you want to restrict what gets inserted,
that's another job for triggers.

>> For security frameworks like SE-PostgreSQL, we might just provide a
>> hook allowing the incoming query tree to be modified, and let the hook
>> function check whether each table in the query has row-level security
>> enabled, and if so perform a modification equivalent to the above.
>
> One point we have to pay mention is all the row-level filter conditions
> have to be evaluated before all the user given condition, except for
> operators pulled-up to index accesses.
> It allows malicious row-cost functions to leak "invisible" tuples anywhere.

We currently have this problem with DAC as well - it means that VIEWs
don't actually work as a security gateway, if the user has the ability
to define a function and pass a WHERE clause to a query against the
view, they can extract the hidden rows.  Fixing it seems like a hard
problem.

>> None of this addresses the issue of doing RLS on system catalogs,
>> which seems like a much harder problem, possibly one that we should
>> just ignore for the first phase of this project.
>
> It is reasonable.
>
> I'd like to point out a few more issues:
>
> * TRUNCATE statement
>
> Truncate is a good feature to clean up the contents of a table.
> But it may contain unremovable tuples. So, it needs to scan a table to
> be truncated once to confirm all the tuples can be removed by the current
> user. It is a trade-off case between performance and security.

I think we should just disallow TRUNCATE in cases where this might be
an issue.  If you want a slow and painful way to get rid of your table
contents, use DELETE.  Or at least, I'd start by doing it this way and
then we can think about whether there's enough benefit to doing what
you're suggesting later.

> * Foreign Key constraint(1)
>
> I don't think upcoming label-based MAC feature support covert channel issue.
> Even if PK is invisible, we can guess PK exists from FK. In fact, commercial
> database products (such as Oracle Label Security) also does not care about.

While I can't speak for anyone else, I don't have a problem not caring
about this.

> * Foreign Key constraint(2)
>
> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
> If associated tuples are filtered out, it breaks reference integrity.
> So, we have to apply special care. In SE-PgSQL case, it raises an error
> instead of filtering during FK checks. And, row-level security hook is
> called at the last for each tuples, unlike normal cases.

Perfecting referential integrity here seems like a pretty tough
problem, but it's likely not necessary to solve it in order to get an
implementation of row-level security that is useful for some purposes.

...Robert



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2009-12-14 04:57         ` KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-14 04:57 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

Robert Haas wrote:
> On Sun, Dec 13, 2009 at 3:50 AM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:
>> Basically, right. In my branch, SE-PgSQL put its hook after all the BR
>> trigger invocations.
>>
>> http://code.google.com/p/sepgsql/source/browse/branches/pgsql-8.4.x/sepgsql/src/backend/executor/exe...
>>
>> But we have another approach. When RelationBuildTriggers() initializes
>> TriggerDesc of Relation, we can inject security hook as a special BR-trigger
>> at the last. If we initialize it here, we don't need to modify COPY FROM
>> implementation, not only INSERT.
>>
>> The reason why I didn't apply this approach is it needs more modification
>> to the core routines, so it makes harder to manage out-of-tree code.
> 
> That's definitely something to consider if it's true.  Why did it
> require more modification of the core routines?

In my local branch, it just adds two lines as follows:
  + /* SELinux labeling and permission checks */
  + sepgsql_heap_insert(resultRelationDesc, tuple);

It is obviously less than modify RelationBuildTriggers() to allocate an
additional slot for the TrigDesc array and put an entry.
The reason was just from the perspective to maintain out-of-tree code,
but different perspective will be necessary to propose a featuer to upstream.

>> One point. MAC is "mandatory", so the table owner should not be able to
>> control whether row-level checks are applied, or not.
>> So, I used a special purpose system column to represent security label.
>> It is generated for each tables, and no additional storage consumption
>> when MAC feature is disabled.
> 
> My current feeling is that a special-purpose system column is not the
> best approach.  I don't see what we gain by doing it that way.  Even
> in an SE-PostgreSQL environment, row-level security might not be
> desired on every table - after all, we've been told that SE-PostgreSQL
> is useful without any row-level security AT ALL, so it's not hard to
> think there could be environments where only some tables need to
> protected.  So I think we want to have a way to turn it on and off on
> a per-table basis.
> 
> Of course, as you point out, we have to make sure that anyone who
> tries to turn RLS on or off for a particular table is authorized to
> perform that operation.  But that's a separate problem which is I
> don't think has much to do with row-level security.

Yes, it is a separate problem not to be concluded at the moment.
(Perhaps, it depends on security model. In DAC, per-table basis is preferable.)

So, I'd like to bring up just an issue to be discussed later.
When we build a binary with a label-based MAC, such as SE-PgSQL, it shall
be turned on/off in the startup time.
(I don't assume it should be configurable in runtime.)

If we set up database cluster without any label-based MAC, all the tuple
shall not have any security label. If the security label is stored within
regular column, we have to modify schema for any tables at first.
If system column provides a security label of tuple, we can dynamically
generate an appropriate security label. In SELinux case, it assumes any
unlabeled objects performs as if it has a pseudo security label:
  system_u:object_r:unlabeled_t:s0

Needless to say, we need to assign appropriate security labels for
meaningful access controls later, but it does not require any schema
changes, even if we repeat to turn on/off the label-based MAC feature.

When label-based MAC feature is disabled, this system column can return
a pseudo value such as NULL or empty string.

>>> #3 seems a little bit trickier.  I don't think the GRANT ... WHERE
>>> syntax is going to be very easy to use.  For constraint-based
>>> row-security, I think we should have something more like:
>>>
>>> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
>>> WHERE where-clause
>>>
>>> (This suffers from the same problem as DELETE ... USING, namely that
>>> sometimes you want an outer join between table and othertable.)
>>>
>>> This gives the user a convenient way to insert a join against one or
>>> more side tables if they are so inclined.
>> Is it reasonably possible to implement USING clause, even if row-level
>> security is applied on COPY FROM/TO statement?
>> And, isn't it necessary to specify condition to apply the filter?
>> (such as select, update and delete)
> 
> The filter is the WHERE clause.  I would think that the operation
> being performed (select, update, delete) wouldn't enter into it.  This
> part is just to decide which tuples will actually be accessible AT
> ALL.  If you want to further prevent certain tuples that are being
> accessed from being update or deleted, you can use a trigger for that
> (possibly one of the global, always-applied-last triggers discussed
> above).
> 
> For INSERT and COPY, I don't think that the ALTER TABLE ... ADD ROW
> FILTER stuff would apply.  If you want to restrict what gets inserted,
> that's another job for triggers.

Are you talking about COPY TO, not only COPY FROM?
For INSERT and COPY FROM, I agree with the direction. Access controls
(and labeling) should be applied on the BR trigger functions.

But COPY TO should filter violated tuples in proper way, because it
can be a big bypass for row-level access controls.
If WHERE clause does not refer any other relations, it is not a difficult
to handle correctly.

>>> For security frameworks like SE-PostgreSQL, we might just provide a
>>> hook allowing the incoming query tree to be modified, and let the hook
>>> function check whether each table in the query has row-level security
>>> enabled, and if so perform a modification equivalent to the above.
>> One point we have to pay mention is all the row-level filter conditions
>> have to be evaluated before all the user given condition, except for
>> operators pulled-up to index accesses.
>> It allows malicious row-cost functions to leak "invisible" tuples anywhere.
> 
> We currently have this problem with DAC as well - it means that VIEWs
> don't actually work as a security gateway, if the user has the ability
> to define a function and pass a WHERE clause to a query against the
> view, they can extract the hidden rows.  Fixing it seems like a hard
> problem.

Yes, we need to consider reasonable solution for the matter.

>> * TRUNCATE statement
>>
>> Truncate is a good feature to clean up the contents of a table.
>> But it may contain unremovable tuples. So, it needs to scan a table to
>> be truncated once to confirm all the tuples can be removed by the current
>> user. It is a trade-off case between performance and security.
> 
> I think we should just disallow TRUNCATE in cases where this might be
> an issue.  If you want a slow and painful way to get rid of your table
> contents, use DELETE.  Or at least, I'd start by doing it this way and
> then we can think about whether there's enough benefit to doing what
> you're suggesting later.

It seems to me the uniformed-disallow is more painfull than violation checks
on the table to be truncated. At least, we should provide an option to check
the table to be truncated does not contain any unremovable tuples when row-
level checks are activated.

However, as you pointed out, it is not a first issue to be resolved.
It may be a todo feature.

>> * Foreign Key constraint(2)
>>
>> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
>> If associated tuples are filtered out, it breaks reference integrity.
>> So, we have to apply special care. In SE-PgSQL case, it raises an error
>> instead of filtering during FK checks. And, row-level security hook is
>> called at the last for each tuples, unlike normal cases.
> 
> Perfecting referential integrity here seems like a pretty tough
> problem, but it's likely not necessary to solve it in order to get an
> implementation of row-level security that is useful for some purposes.

Is the approach in SE-PgSQL suitable for the issue?
It can prevent to update/delete tuple referenced by invisible tuples.

We have two modes in row-level security.
The first is filtering-mode. It applies security policy function prior
to any other user given conditions, and filters out violated tuples from
the result set.
The second is aborting-mode. It is only used by internal stuff which does
not provide any malicious function in the condition. It applies security
policy function next to all the WHERE clause, and raises an error if the
query tries to refer violated tuples.

Thanks,
-- 
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-14 05:35           ` Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2009-12-14 05:35 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

2009/12/13 KaiGai Kohei <kaigai@ak.jp.nec.com>:
> Robert Haas wrote:
>> On Sun, Dec 13, 2009 at 3:50 AM, KaiGai Kohei <kaigai@kaigai.gr.jp> wrote:
>>> Basically, right. In my branch, SE-PgSQL put its hook after all the BR
>>> trigger invocations.
>>>
>>> http://code.google.com/p/sepgsql/source/browse/branches/pgsql-8.4.x/sepgsql/src/backend/executor/exe...
>>>
>>> But we have another approach. When RelationBuildTriggers() initializes
>>> TriggerDesc of Relation, we can inject security hook as a special BR-trigger
>>> at the last. If we initialize it here, we don't need to modify COPY FROM
>>> implementation, not only INSERT.
>>>
>>> The reason why I didn't apply this approach is it needs more modification
>>> to the core routines, so it makes harder to manage out-of-tree code.
>>
>> That's definitely something to consider if it's true.  Why did it
>> require more modification of the core routines?
>
> In my local branch, it just adds two lines as follows:
>  + /* SELinux labeling and permission checks */
>  + sepgsql_heap_insert(resultRelationDesc, tuple);
>
> It is obviously less than modify RelationBuildTriggers() to allocate an
> additional slot for the TrigDesc array and put an entry.
> The reason was just from the perspective to maintain out-of-tree code,
> but different perspective will be necessary to propose a featuer to upstream.

Yes, the difference in code impact between those two will not be
relevant for upstream, I think.

>>> One point. MAC is "mandatory", so the table owner should not be able to
>>> control whether row-level checks are applied, or not.
>>> So, I used a special purpose system column to represent security label.
>>> It is generated for each tables, and no additional storage consumption
>>> when MAC feature is disabled.
>>
>> My current feeling is that a special-purpose system column is not the
>> best approach.  I don't see what we gain by doing it that way.  Even
>> in an SE-PostgreSQL environment, row-level security might not be
>> desired on every table - after all, we've been told that SE-PostgreSQL
>> is useful without any row-level security AT ALL, so it's not hard to
>> think there could be environments where only some tables need to
>> protected.  So I think we want to have a way to turn it on and off on
>> a per-table basis.
>>
>> Of course, as you point out, we have to make sure that anyone who
>> tries to turn RLS on or off for a particular table is authorized to
>> perform that operation.  But that's a separate problem which is I
>> don't think has much to do with row-level security.
>
> Yes, it is a separate problem not to be concluded at the moment.
> (Perhaps, it depends on security model. In DAC, per-table basis is preferable.)

Even for MAC, it might be desirable to turn it off on codes tables or
the like, to minimize the performance hit.  But we can defer this
question to another day.

> So, I'd like to bring up just an issue to be discussed later.
> When we build a binary with a label-based MAC, such as SE-PgSQL, it shall
> be turned on/off in the startup time.
> (I don't assume it should be configurable in runtime.)

I don't see any real reason why it couldn't be configurable at
runtime, but I don't have a terribly strong opinion at this point.  I
might have an opinion later when I'm more informed.

> If we set up database cluster without any label-based MAC, all the tuple
> shall not have any security label. If the security label is stored within
> regular column, we have to modify schema for any tables at first.
> If system column provides a security label of tuple, we can dynamically
> generate an appropriate security label. In SELinux case, it assumes any
> unlabeled objects performs as if it has a pseudo security label:
>  system_u:object_r:unlabeled_t:s0
>
> Needless to say, we need to assign appropriate security labels for
> meaningful access controls later, but it does not require any schema
> changes, even if we repeat to turn on/off the label-based MAC feature.
>
> When label-based MAC feature is disabled, this system column can return
> a pseudo value such as NULL or empty string.

I think you are wrong about all of this.  To add security labels to
existing tuples, you're going to need to rewrite the table, period.
Whether you're adding a column in the process or just populating the
contents of a previous-omitted column doesn't seem particularly
relevant.  Similarly you can insert a pseudo security label when the
column is missing just as well as you can when it's present but
unpopulated.

>>>> #3 seems a little bit trickier.  I don't think the GRANT ... WHERE
>>>> syntax is going to be very easy to use.  For constraint-based
>>>> row-security, I think we should have something more like:
>>>>
>>>> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
>>>> WHERE where-clause
>>>>
>>>> (This suffers from the same problem as DELETE ... USING, namely that
>>>> sometimes you want an outer join between table and othertable.)
>>>>
>>>> This gives the user a convenient way to insert a join against one or
>>>> more side tables if they are so inclined.
>>> Is it reasonably possible to implement USING clause, even if row-level
>>> security is applied on COPY FROM/TO statement?
>>> And, isn't it necessary to specify condition to apply the filter?
>>> (such as select, update and delete)
>>
>> The filter is the WHERE clause.  I would think that the operation
>> being performed (select, update, delete) wouldn't enter into it.  This
>> part is just to decide which tuples will actually be accessible AT
>> ALL.  If you want to further prevent certain tuples that are being
>> accessed from being update or deleted, you can use a trigger for that
>> (possibly one of the global, always-applied-last triggers discussed
>> above).
>>
>> For INSERT and COPY, I don't think that the ALTER TABLE ... ADD ROW
>> FILTER stuff would apply.  If you want to restrict what gets inserted,
>> that's another job for triggers.
>
> Are you talking about COPY TO, not only COPY FROM?
> For INSERT and COPY FROM, I agree with the direction. Access controls
> (and labeling) should be applied on the BR trigger functions.

OK.  Yes, that's what I meant.

> But COPY TO should filter violated tuples in proper way, because it
> can be a big bypass for row-level access controls.

Good point.  If a table has row filtering enabled, we'll have to
convert COPY FROM <table> to COPY FROM (SELECT * FROM <table>).  That
should be enough to make the row filters kick in.

> If WHERE clause does not refer any other relations, it is not a difficult
> to handle correctly.

Even if it does refer to other relations I think it's fine, under the
above approach.

[snip]

>>> * Foreign Key constraint(2)
>>>
>>> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
>>> If associated tuples are filtered out, it breaks reference integrity.
>>> So, we have to apply special care. In SE-PgSQL case, it raises an error
>>> instead of filtering during FK checks. And, row-level security hook is
>>> called at the last for each tuples, unlike normal cases.
>>
>> Perfecting referential integrity here seems like a pretty tough
>> problem, but it's likely not necessary to solve it in order to get an
>> implementation of row-level security that is useful for some purposes.
>
> Is the approach in SE-PgSQL suitable for the issue?
> It can prevent to update/delete tuple referenced by invisible tuples.
>
> We have two modes in row-level security.
> The first is filtering-mode. It applies security policy function prior
> to any other user given conditions, and filters out violated tuples from
> the result set.
> The second is aborting-mode. It is only used by internal stuff which does
> not provide any malicious function in the condition. It applies security
> policy function next to all the WHERE clause, and raises an error if the
> query tries to refer violated tuples.

Hmm... the idea of having two modes doesn't sound right off the top of
my head.  But I think we have a long time before we need worry about
this.  We have neither SE-PostgreSQL nor RLS in core, nor are either
one anywhere close to being merged.  So worrying about how the two
will interact when we have both is putting the cart before the horse.
A lot can change between now and then.

...Robert



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2009-12-14 06:43             ` KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 08:58               ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-14 06:43 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

Robert Haas wrote:
>>>> One point. MAC is "mandatory", so the table owner should not be able to
>>>> control whether row-level checks are applied, or not.
>>>> So, I used a special purpose system column to represent security label.
>>>> It is generated for each tables, and no additional storage consumption
>>>> when MAC feature is disabled.
>>> My current feeling is that a special-purpose system column is not the
>>> best approach.  I don't see what we gain by doing it that way.  Even
>>> in an SE-PostgreSQL environment, row-level security might not be
>>> desired on every table - after all, we've been told that SE-PostgreSQL
>>> is useful without any row-level security AT ALL, so it's not hard to
>>> think there could be environments where only some tables need to
>>> protected.  So I think we want to have a way to turn it on and off on
>>> a per-table basis.
>>>
>>> Of course, as you point out, we have to make sure that anyone who
>>> tries to turn RLS on or off for a particular table is authorized to
>>> perform that operation.  But that's a separate problem which is I
>>> don't think has much to do with row-level security.
>> Yes, it is a separate problem not to be concluded at the moment.
>> (Perhaps, it depends on security model. In DAC, per-table basis is preferable.)
> 
> Even for MAC, it might be desirable to turn it off on codes tables or
> the like, to minimize the performance hit.  But we can defer this
> question to another day.

Yes, I provide sepgsql_row_level guc in my local branch to turn on/off
its row-level controls. It allows to reduce performance penalty related
to RLS and reduce storage consumption for security labels. (It requires
additional sizeof(Oid) bytes for each tuples.)
The point is this guc option is configurable from the only administrator
who can edit $PGDATA/postgresql.conf.

But it is an implementation detail not to be concluded at the moment.

>> If we set up database cluster without any label-based MAC, all the tuple
>> shall not have any security label. If the security label is stored within
>> regular column, we have to modify schema for any tables at first.
>> If system column provides a security label of tuple, we can dynamically
>> generate an appropriate security label. In SELinux case, it assumes any
>> unlabeled objects performs as if it has a pseudo security label:
>>  system_u:object_r:unlabeled_t:s0
>>
>> Needless to say, we need to assign appropriate security labels for
>> meaningful access controls later, but it does not require any schema
>> changes, even if we repeat to turn on/off the label-based MAC feature.
>>
>> When label-based MAC feature is disabled, this system column can return
>> a pseudo value such as NULL or empty string.
> 
> I think you are wrong about all of this.  To add security labels to
> existing tuples, you're going to need to rewrite the table, period.
> Whether you're adding a column in the process or just populating the
> contents of a previous-omitted column doesn't seem particularly
> relevant.  Similarly you can insert a pseudo security label when the
> column is missing just as well as you can when it's present but
> unpopulated.

For system catalogs, we cannot touch its schema with a light heart,
even if active enhanced security provider is switched or turned on/off.
If we define a common system column for all the label-based MAC,
it can be available for both of user tables and system catalogs
without any table-rewrite process.

But it is an implementation detail not to be concluded at the moment.

>>>>> #3 seems a little bit trickier.  I don't think the GRANT ... WHERE
>>>>> syntax is going to be very easy to use.  For constraint-based
>>>>> row-security, I think we should have something more like:
>>>>>
>>>>> ALTER TABLE table ADD ROW FILTER filtername USING othertable [, ...]
>>>>> WHERE where-clause
>>>>>
>>>>> (This suffers from the same problem as DELETE ... USING, namely that
>>>>> sometimes you want an outer join between table and othertable.)
>>>>>
>>>>> This gives the user a convenient way to insert a join against one or
>>>>> more side tables if they are so inclined.
>>>> Is it reasonably possible to implement USING clause, even if row-level
>>>> security is applied on COPY FROM/TO statement?
>>>> And, isn't it necessary to specify condition to apply the filter?
>>>> (such as select, update and delete)
>>> The filter is the WHERE clause.  I would think that the operation
>>> being performed (select, update, delete) wouldn't enter into it.  This
>>> part is just to decide which tuples will actually be accessible AT
>>> ALL.  If you want to further prevent certain tuples that are being
>>> accessed from being update or deleted, you can use a trigger for that
>>> (possibly one of the global, always-applied-last triggers discussed
>>> above).
>>>
>>> For INSERT and COPY, I don't think that the ALTER TABLE ... ADD ROW
>>> FILTER stuff would apply.  If you want to restrict what gets inserted,
>>> that's another job for triggers.
>> Are you talking about COPY TO, not only COPY FROM?
>> For INSERT and COPY FROM, I agree with the direction. Access controls
>> (and labeling) should be applied on the BR trigger functions.
> 
> OK.  Yes, that's what I meant.
> 
[..snip..]

>> But COPY TO should filter violated tuples in proper way, because it
>> can be a big bypass for row-level access controls.
> 
> Good point.  If a table has row filtering enabled, we'll have to
> convert COPY FROM <table> to COPY FROM (SELECT * FROM <table>).  That
> should be enough to make the row filters kick in.

Wow, it seems to me a good idea.

>>>> * Foreign Key constraint(2)
>>>>
>>>> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
>>>> If associated tuples are filtered out, it breaks reference integrity.
>>>> So, we have to apply special care. In SE-PgSQL case, it raises an error
>>>> instead of filtering during FK checks. And, row-level security hook is
>>>> called at the last for each tuples, unlike normal cases.
>>> Perfecting referential integrity here seems like a pretty tough
>>> problem, but it's likely not necessary to solve it in order to get an
>>> implementation of row-level security that is useful for some purposes.
>> Is the approach in SE-PgSQL suitable for the issue?
>> It can prevent to update/delete tuple referenced by invisible tuples.
>>
>> We have two modes in row-level security.
>> The first is filtering-mode. It applies security policy function prior
>> to any other user given conditions, and filters out violated tuples from
>> the result set.
>> The second is aborting-mode. It is only used by internal stuff which does
>> not provide any malicious function in the condition. It applies security
>> policy function next to all the WHERE clause, and raises an error if the
>> query tries to refer violated tuples.
> 
> Hmm... the idea of having two modes doesn't sound right off the top of
> my head.  But I think we have a long time before we need worry about
> this.  We have neither SE-PostgreSQL nor RLS in core, nor are either
> one anywhere close to being merged.  So worrying about how the two
> will interact when we have both is putting the cart before the horse.
> A lot can change between now and then.

IIRC, I've not gotten any opposition about this two-modes design.
Most of arguments about RLS were information leaks via covert-channels
which allows us to estimate an existence of invisible PK/FK.
But we don't define it as a problem to be resolved.

Thanks,
-- 
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-14 08:58               ` KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 15:04                 ` Re: Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-14 08:58 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

One more issue I found.

What row-level policy should be applied on inherited tables?

If inconsistent policy is applied on the parent and child table,
we can see different result set, although a part of result set
in the parent table come from the child table.

My idea is to copy row-level policies to inherited tables from the
parent table. We can additional row-level policy on the inherited
tables, but all the condition is chained by AND, so here is no
inconsistency.
Even if the inherited table has multiple parents, all the row-level
policies shall be applied, so here is no inconsistency.
(Needless to say, child table have same columns, so we can apply
same row-level policies.)

Thanks,
-- 
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 08:58               ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-14 15:04                 ` Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 0 replies; 165+ messages in thread

From: Tom Lane @ 2009-12-14 15:04 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

KaiGai Kohei <kaigai@ak.jp.nec.com> writes:
> One more issue I found.
> What row-level policy should be applied on inherited tables?

Yup, that seems like an interesting problem.

> Even if the inherited table has multiple parents, all the row-level
> policies shall be applied, so here is no inconsistency.
> (Needless to say, child table have same columns, so we can apply
> same row-level policies.)

I don't think I believe either of those statements.

			regards, tom lane



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-14 11:18               ` Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Robert Haas @ 2009-12-14 11:18 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

2009/12/14 KaiGai Kohei <kaigai@ak.jp.nec.com>:
> Robert Haas wrote:
>>>>> One point. MAC is "mandatory", so the table owner should not be able to
>>>>> control whether row-level checks are applied, or not.
>>>>> So, I used a special purpose system column to represent security label.
>>>>> It is generated for each tables, and no additional storage consumption
>>>>> when MAC feature is disabled.
>>>> My current feeling is that a special-purpose system column is not the
>>>> best approach.  I don't see what we gain by doing it that way.  Even
>>>> in an SE-PostgreSQL environment, row-level security might not be
>>>> desired on every table - after all, we've been told that SE-PostgreSQL
>>>> is useful without any row-level security AT ALL, so it's not hard to
>>>> think there could be environments where only some tables need to
>>>> protected.  So I think we want to have a way to turn it on and off on
>>>> a per-table basis.
>>>>
>>>> Of course, as you point out, we have to make sure that anyone who
>>>> tries to turn RLS on or off for a particular table is authorized to
>>>> perform that operation.  But that's a separate problem which is I
>>>> don't think has much to do with row-level security.
>>> Yes, it is a separate problem not to be concluded at the moment.
>>> (Perhaps, it depends on security model. In DAC, per-table basis is preferable.)
>>
>> Even for MAC, it might be desirable to turn it off on codes tables or
>> the like, to minimize the performance hit.  But we can defer this
>> question to another day.
>
> Yes, I provide sepgsql_row_level guc in my local branch to turn on/off
> its row-level controls. It allows to reduce performance penalty related
> to RLS and reduce storage consumption for security labels. (It requires
> additional sizeof(Oid) bytes for each tuples.)
> The point is this guc option is configurable from the only administrator
> who can edit $PGDATA/postgresql.conf.
>
> But it is an implementation detail not to be concluded at the moment.

Well, that would be a global switch, not per table.

>>> If we set up database cluster without any label-based MAC, all the tuple
>>> shall not have any security label. If the security label is stored within
>>> regular column, we have to modify schema for any tables at first.
>>> If system column provides a security label of tuple, we can dynamically
>>> generate an appropriate security label. In SELinux case, it assumes any
>>> unlabeled objects performs as if it has a pseudo security label:
>>>  system_u:object_r:unlabeled_t:s0
>>>
>>> Needless to say, we need to assign appropriate security labels for
>>> meaningful access controls later, but it does not require any schema
>>> changes, even if we repeat to turn on/off the label-based MAC feature.
>>>
>>> When label-based MAC feature is disabled, this system column can return
>>> a pseudo value such as NULL or empty string.
>>
>> I think you are wrong about all of this.  To add security labels to
>> existing tuples, you're going to need to rewrite the table, period.
>> Whether you're adding a column in the process or just populating the
>> contents of a previous-omitted column doesn't seem particularly
>> relevant.  Similarly you can insert a pseudo security label when the
>> column is missing just as well as you can when it's present but
>> unpopulated.
>
> For system catalogs, we cannot touch its schema with a light heart,
> even if active enhanced security provider is switched or turned on/off.
> If we define a common system column for all the label-based MAC,
> it can be available for both of user tables and system catalogs
> without any table-rewrite process.
>
> But it is an implementation detail not to be concluded at the moment.

Err... well, as I said upthread: "None of this addresses the issue of
doing RLS on system catalogs, which seems like a much harder problem,
possibly one that we should just ignore for the first phase of this
project."  So yeah, I agree: it won't work for system catalogs.

[snip]

>>>>> * Foreign Key constraint(2)
>>>>>
>>>>> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
>>>>> If associated tuples are filtered out, it breaks reference integrity.
>>>>> So, we have to apply special care. In SE-PgSQL case, it raises an error
>>>>> instead of filtering during FK checks. And, row-level security hook is
>>>>> called at the last for each tuples, unlike normal cases.
>>>> Perfecting referential integrity here seems like a pretty tough
>>>> problem, but it's likely not necessary to solve it in order to get an
>>>> implementation of row-level security that is useful for some purposes.
>>> Is the approach in SE-PgSQL suitable for the issue?
>>> It can prevent to update/delete tuple referenced by invisible tuples.
>>>
>>> We have two modes in row-level security.
>>> The first is filtering-mode. It applies security policy function prior
>>> to any other user given conditions, and filters out violated tuples from
>>> the result set.
>>> The second is aborting-mode. It is only used by internal stuff which does
>>> not provide any malicious function in the condition. It applies security
>>> policy function next to all the WHERE clause, and raises an error if the
>>> query tries to refer violated tuples.
>>
>> Hmm... the idea of having two modes doesn't sound right off the top of
>> my head.  But I think we have a long time before we need worry about
>> this.  We have neither SE-PostgreSQL nor RLS in core, nor are either
>> one anywhere close to being merged.  So worrying about how the two
>> will interact when we have both is putting the cart before the horse.
>> A lot can change between now and then.
>
> IIRC, I've not gotten any opposition about this two-modes design.
> Most of arguments about RLS were information leaks via covert-channels
> which allows us to estimate an existence of invisible PK/FK.
> But we don't define it as a problem to be resolved.

I know that was one of Tom's concerns.  Personally, my concerns are:

1. I want to implement row-level security in a way that is useful for
people who don't care about SE-PostgreSQL.  I think there are lots of
people who would be interested in that.  In fact, as Josh said, there
are probably MORE people who are interested in the constraint-based
approach than there are who want label-based security a la
SE-PostgreSQL.

2. I want to implement row-level security in a way that is very
flexible and allows for a wide range of access control policies.  The
core row-level security mechanism should not care about or prejudge a
particular policy - it should just be a mechanism for enforcing
row-filtering.

3. I want to implement row-level security in a way that allows the
planner maximum flexibility in implementing the row filtering that is
needed in a particular case.  SE-PostgreSQL RLS presumes what is
essentially an additional join against the security table ID for every
table in the query - doing this in a way that allows joins to be
reordered or implemented in multiple ways (straight nestloop, nestloop
with inner indexscan, hash join) will drastically improve performance.
 The original implementation didn't actually implement it as a join,
but rather with special-case code that performed the security ID
lookups as part of the heap scan.  That's not going to work for any
kind of row-level security other than SE-PostgreSQL (so, see points 1
and 2) and it's also going to make the performance much worse than it
needs to be.  Granted, the performance is never going to be GOOD, but
we should try to at least make it not ATROCIOUS.

...Robert



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2009-12-14 11:52                 ` KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-14 13:47                   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  0 siblings, 1 reply; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-14 11:52 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: KaiGai Kohei <kaigai@ak.jp.nec.com>; Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; pgsql-hackers

(2009/12/14 20:18), Robert Haas wrote:
>>>>>> * Foreign Key constraint(2)
>>>>>>
>>>>>> FK is implemented as a trigger which internally uses SELECT/UPDATE/DELETE.
>>>>>> If associated tuples are filtered out, it breaks reference integrity.
>>>>>> So, we have to apply special care. In SE-PgSQL case, it raises an error
>>>>>> instead of filtering during FK checks. And, row-level security hook is
>>>>>> called at the last for each tuples, unlike normal cases.
>>>>> Perfecting referential integrity here seems like a pretty tough
>>>>> problem, but it's likely not necessary to solve it in order to get an
>>>>> implementation of row-level security that is useful for some purposes.
>>>> Is the approach in SE-PgSQL suitable for the issue?
>>>> It can prevent to update/delete tuple referenced by invisible tuples.
>>>>
>>>> We have two modes in row-level security.
>>>> The first is filtering-mode. It applies security policy function prior
>>>> to any other user given conditions, and filters out violated tuples from
>>>> the result set.
>>>> The second is aborting-mode. It is only used by internal stuff which does
>>>> not provide any malicious function in the condition. It applies security
>>>> policy function next to all the WHERE clause, and raises an error if the
>>>> query tries to refer violated tuples.
>>>
>>> Hmm... the idea of having two modes doesn't sound right off the top of
>>> my head.  But I think we have a long time before we need worry about
>>> this.  We have neither SE-PostgreSQL nor RLS in core, nor are either
>>> one anywhere close to being merged.  So worrying about how the two
>>> will interact when we have both is putting the cart before the horse.
>>> A lot can change between now and then.
>>
>> IIRC, I've not gotten any opposition about this two-modes design.
>> Most of arguments about RLS were information leaks via covert-channels
>> which allows us to estimate an existence of invisible PK/FK.
>> But we don't define it as a problem to be resolved.
>
> I know that was one of Tom's concerns.  Personally, my concerns are:
>
> 1. I want to implement row-level security in a way that is useful for
> people who don't care about SE-PostgreSQL.  I think there are lots of
> people who would be interested in that.  In fact, as Josh said, there
> are probably MORE people who are interested in the constraint-based
> approach than there are who want label-based security a la
> SE-PostgreSQL.

I also agree it is a right direction. SELinux shall be "one of them" to
make access control decision in row-level.
In my current standpoint, we add general row-level security first, then
SELinux support provides its access control decision function. OK?

> 2. I want to implement row-level security in a way that is very
> flexible and allows for a wide range of access control policies.  The
> core row-level security mechanism should not care about or prejudge a
> particular policy - it should just be a mechanism for enforcing
> row-filtering.

Ditto,

> 3. I want to implement row-level security in a way that allows the
> planner maximum flexibility in implementing the row filtering that is
> needed in a particular case.  SE-PostgreSQL RLS presumes what is
> essentially an additional join against the security table ID for every
> table in the query - doing this in a way that allows joins to be
> reordered or implemented in multiple ways (straight nestloop, nestloop
> with inner indexscan, hash join) will drastically improve performance.
>   The original implementation didn't actually implement it as a join,
> but rather with special-case code that performed the security ID
> lookups as part of the heap scan.  That's not going to work for any
> kind of row-level security other than SE-PostgreSQL (so, see points 1
> and 2) and it's also going to make the performance much worse than it
> needs to be.  Granted, the performance is never going to be GOOD, but
> we should try to at least make it not ATROCIOUS.

The reason why I put on the security hook in ExecScan() is to avoid the
problem that row-cost user defined function can be evaluated earlier
than row-level security policy. (I believed it was a well-known problem
at that time yet.) So, I didn't want to append it before optimization.

I also believe this matter should be resolved when we provide row-level
security stuff, because it is a security feature.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
@ 2009-12-14 13:47                   ` Stephen Frost <sfrost@snowman.net>
  2009-12-15 01:40                     ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Stephen Frost @ 2009-12-14 13:47 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; KaiGai Kohei <kaigai@ak.jp.nec.com>; Josh Berkus <josh@agliodbs.com>; pgsql-hackers

KaiGai,

* KaiGai Kohei (kaigai@kaigai.gr.jp) wrote:
> The reason why I put on the security hook in ExecScan() is to avoid the
> problem that row-cost user defined function can be evaluated earlier
> than row-level security policy. (I believed it was a well-known problem
> at that time yet.) So, I didn't want to append it before optimization.

This is a problem which needs to be addressed and fixed independently.

> I also believe this matter should be resolved when we provide row-level
> security stuff, because it is a security feature.

This issue should be fixed first, not as part of some large-scale patch.

If you have thoughts or ideas about how to address this problem as it
relates to views, I think you would find alot of people willing to
listen and to discuss it.  This must be independent of SELinux,
independent of row-level security, and isn't something based on any of
the patches which have been submitted so far.  None of them that I've
seen resolve this problem in a way that the community is willing to
accept.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (196B, ../../20091214134714.GS17756@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-14 13:47                   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
@ 2009-12-15 01:40                     ` KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-15 02:06                       ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-15 03:32                       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: KaiGai Kohei @ 2009-12-15 01:40 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; Josh Berkus <josh@agliodbs.com>; pgsql-hackers

Stephen Frost wrote:
> KaiGai,
> 
> * KaiGai Kohei (kaigai@kaigai.gr.jp) wrote:
>> The reason why I put on the security hook in ExecScan() is to avoid the
>> problem that row-cost user defined function can be evaluated earlier
>> than row-level security policy. (I believed it was a well-known problem
>> at that time yet.) So, I didn't want to append it before optimization.
> 
> This is a problem which needs to be addressed and fixed independently.
> 
>> I also believe this matter should be resolved when we provide row-level
>> security stuff, because it is a security feature.
> 
> This issue should be fixed first, not as part of some large-scale patch.
> 
> If you have thoughts or ideas about how to address this problem as it
> relates to views, I think you would find alot of people willing to
> listen and to discuss it.  This must be independent of SELinux,
> independent of row-level security, and isn't something based on any of
> the patches which have been submitted so far.  None of them that I've
> seen resolve this problem in a way that the community is willing to
> accept.

Sorry, I don't have something good idea at the moment.

IIRC, one headache issue is that user may provide well indexable conditions,
such as "SELECT * FROM view_x WHERE id = 1234". In this case, if we strictly
keep the order of evaluation between inside and outside of the view, its
performance penalty will over reasonable tradeoff to the better security.

Someone pointed out user given conditions which can be replaced by index scan
are "trusted", so all we need to focus on are conditions which need to check
for each tuples. I also think it is a right direction, as long as we can
restrict who can define index access method in appropriate way.

If we can focus on the order of evaluation on the non-indexed conditions,
the point is order_qual_clauses() which sort the given qual list based on
the cost evaluation. If we can mark condition node a flag which means this
node come from inside of view or row-level policy, it is not difficult to
keep evaluation order.

However, it is just my quick idea. It might miss something.
We need to consider the matter for more details...
-- 
OSS Platform Development Division, NEC
KaiGai Kohei <kaigai@ak.jp.nec.com>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-14 13:47                   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-15 01:40                     ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-15 02:06                       ` Stephen Frost <sfrost@snowman.net>
  1 sibling, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2009-12-15 02:06 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: KaiGai Kohei <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; Josh Berkus <josh@agliodbs.com>; pgsql-hackers

KaiGai,

* KaiGai Kohei (kaigai@ak.jp.nec.com) wrote:
> IIRC, one headache issue is that user may provide well indexable conditions,
> such as "SELECT * FROM view_x WHERE id = 1234". In this case, if we strictly
> keep the order of evaluation between inside and outside of the view, its
> performance penalty will over reasonable tradeoff to the better security.
> 
> Someone pointed out user given conditions which can be replaced by index scan
> are "trusted", so all we need to focus on are conditions which need to check
> for each tuples. I also think it is a right direction, as long as we can
> restrict who can define index access method in appropriate way.

It sounds like that might help, but I feel that a whole solution will be
more complex than just differentiating between seq scan nodes and index
scan ones.

> If we can focus on the order of evaluation on the non-indexed conditions,
> the point is order_qual_clauses() which sort the given qual list based on
> the cost evaluation. If we can mark condition node a flag which means this
> node come from inside of view or row-level policy, it is not difficult to
> keep evaluation order.

Identifying where this matters is important.  Anyone have suggestions on
how to do that?  There was some discussion on IRC about that but it
didn't really go anywhere.  I don't like the idea of presuming the user
will always want to limit the planner in this way.  Perhaps we can
convince ourselves, once we have an implementation, that it doesn't
poorly affect performance (the primary reason to avoid constraining the
planner), or that it's what our users would really want (I might be able
to buy off on this..), but I doubt it.

A couple of options about how the user could ask us to constrain the
planning to eliminate this issue are, off-hand:
Global GUC which enables/disables
Attribute of the view, perhaps indicated as 'CREATE SECURITY VIEW' or
similar
Something in the definition of the WHERE clause, eg: select * from x
where security(q = 50);

Anyone have thoughts about this?  Perhaps it's too early to discuss
this anyway, just trying to keep the discussion moving in some way.

> However, it is just my quick idea. It might miss something.
> We need to consider the matter for more details...

I agree, this needs more thought and input from others who are very
familiar with the planner, executor, etc.  Additionally, this needs
to be done before we can really go anywhere with row-level security.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (196B, ../../20091215020652.GV17756@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-14 13:47                   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-15 01:40                     ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
@ 2009-12-15 03:32                       ` Robert Haas <robertmhaas@gmail.com>
  2009-12-15 12:20                         ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  1 sibling, 1 reply; 165+ messages in thread

From: Robert Haas @ 2009-12-15 03:32 UTC (permalink / raw)
  To: KaiGai Kohei <kaigai@ak.jp.nec.com>; +Cc: Stephen Frost <sfrost@snowman.net>; KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; pgsql-hackers

2009/12/14 KaiGai Kohei <kaigai@ak.jp.nec.com>:
> IIRC, one headache issue is that user may provide well indexable conditions,
> such as "SELECT * FROM view_x WHERE id = 1234". In this case, if we strictly
> keep the order of evaluation between inside and outside of the view, its
> performance penalty will over reasonable tradeoff to the better security.

If you don't allow the indexable qual to be pushed down into the view
in this situation, performance will be wretched.  I think we need to
distinguish between trusted and untrusted operations.  Everything in
the view definition is trusted.  And some other things... perhaps
access methods and some/most/all system catalog functions... are
trusted.  Other stuff is untrusted, and can't be pushed down.

I think there was a previous discussion of this when Heikki first
posted the issue to -hackers.

...Robert



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: Row-Level Security
  2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-13 00:41 ` Re: Row-Level Security Josh Berkus <josh@agliodbs.com>
  2009-12-13 03:18   ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-13 08:50     ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-13 12:11       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 04:57         ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 05:35           ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 06:43             ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-14 11:18               ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2009-12-14 11:52                 ` Re: Row-Level Security KaiGai Kohei <kaigai@kaigai.gr.jp>
  2009-12-14 13:47                   ` Re: Row-Level Security Stephen Frost <sfrost@snowman.net>
  2009-12-15 01:40                     ` Re: Row-Level Security KaiGai Kohei <kaigai@ak.jp.nec.com>
  2009-12-15 03:32                       ` Re: Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2009-12-15 12:20                         ` Stephen Frost <sfrost@snowman.net>
  0 siblings, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2009-12-15 12:20 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: KaiGai Kohei <kaigai@ak.jp.nec.com>; KaiGai Kohei <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; pgsql-hackers

* Robert Haas (robertmhaas@gmail.com) wrote:
> I think there was a previous discussion of this when Heikki first
> posted the issue to -hackers.

There was, it's now linked off the http://wiki.postgresql.org/wiki/RLS
page (as well as this thread).  Feel free to add other threads, update
with your thoughts, summarize what the thread conclusions were, etc...
Otherwise I'll have to. ;)  (Seriously, I'm planning to, but if you
could take a peek at what I've put up there so far, I wouldn't
complain).

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (196B, ../../20091215122005.GX17756@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* [v9.3] Row-Level Security
@ 2012-06-14 15:43 Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-14 15:43 UTC (permalink / raw)
  To: Florian Pflug <fgp@phlo.org>; Robert Haas <robertmhaas@gmail.com>; +Cc: pgsql-hackers

The attached patch provides bare row-level security feature.

Table's owner can set its own security policy using the following syntax:
  ALTER TABLE <table> SET ROW LEVEL SECURITY (<condition>);
  ALTER TABLE <table> RESET ROW LEVEL SECURITY;

The condition must be an expression that returns a boolean value
and can reference contents of each rows. (Right now, it does not
support to contain SubLink in the expression; to be improved)

In the previous discussion, we planned to add a syntax option to
clarify the command type to fire the RLS policy, such as FOR UPDATE.
But current my opinion is, it is not necessary. For example, we can
reference the contents of rows being updated / deleted using
RETURNING clause. So, it does not make sense to have different
RLS policy at UPDATE / DELETE from SELECT.

If and when user's query (SELECT, UPDATE or DELETE, not INSERT)
references the relation with RLS policy, only rows that satisfies the
supplied condition are available to access.
It performs as if the configured condition was implicitly added to
the WHERE clause, however, this mechanism tries to replace
references to the table with RLS policy by a simple sub-query
that scans the target table with RLS policy, to ensure the policy
condition is evaluated earlier than any other user given qualifier.

EXPLAIN shows how RLS works.

postgres=# ALTER TABLE sample SET ROW LEVEL SECURITY (z > current_date - 10);
ALTER TABLE

postgres=# EXPLAIN SELECT * FROM sample WHERE f_leak(y);
                                     QUERY PLAN
------------------------------------------------------------------------------------
 Subquery Scan on sample  (cost=0.00..42.54 rows=215 width=40)
   Filter: f_leak(sample.y)
   ->  Seq Scan on sample  (cost=0.00..36.10 rows=644 width=66)
         Filter: ((z > (('now'::cstring)::date - 10)) OR
has_superuser_privilege())
(4 rows)

In above example, the security policy does not allow to reference
rows earlier than 10 days. Then, SELECT on the table was
expanded to a sub-query and configured expression was added
inside of the sub-query. Database superuser can bypass any
security checks, so "OR has_superuser_privilege()" was automatically
attached in addition to user configured expression.

On the other hand, I'm not 100% sure about my design to restrict
rows to be updated and deleted. Similarly, it expands the target
relation of UPDATE or DELETE statement into a sub-query with
condition. ExecModifyTable() pulls a tuple from the sub-query,
instead of regular table, so it seems to me working at least, but
I didn't try all the possible cases of course.

postgres=# EXPLAIN UPDATE sample SET y = y || '_updt' WHERE f_leak(y);
                                        QUERY PLAN
------------------------------------------------------------------------------------------
 Update on sample  (cost=0.00..43.08 rows=215 width=46)
   ->  Subquery Scan on sample  (cost=0.00..43.08 rows=215 width=46)
         Filter: f_leak(sample.y)
         ->  Seq Scan on sample  (cost=0.00..36.10 rows=644 width=66)
               Filter: ((z > (('now'::cstring)::date - 10)) OR
has_superuser_privilege())
(5 rows)

I have two other ideas to implement writer side RLS.

The first idea modifies WHERE clause to satisfies RLS policy, but Robert
concerned about this idea in the previous discussion, because it takes
twice scans.
  UPDATE sample SET y = y || '_updt' WHERE f_leak(y);
shall be modified to:
  UPDATE  sample SET y = y || '_updt' WHERE ctid = (
      SELECT ctid FROM (
          SELECT ctid, * FROM sample WHERE <RLS policy>
      ) AS pseudo_sample WHERE f_leak(y)
  );
Although the outer scan is ctid scan, it takes seq-scan at first level.


The second idea tries to duplicate RangeTblEntry of the target relation
to be updated or deleted, then one perform as target relation as is, and
the other performs as data source to produce older version of tuples;
being replaced by a sub-query with RLS condition.
I didn't try the second idea yet. As long as we can patch the code that
assumes the target relation has same rtindex with source relation, it
might be safe approach. However, I'm not sure which is less invasive
approach compared to the current patch.


Of course, here is some limitations, to keep the patch size reasonable
level to review.
- The permission to bypass RLS policy was under discussion.
  If and when we should provide a special permission to bypass RLS
  policy, the "OR has_superuser_privilege()" shall be replaced by
  "OR has_table_privilege(tableoid, 'RLSBYPASS')".
  Right now, I allows only superuser to bypass RLS policy.
- This patch focuses on the bare feature only, not any enhancement
  at query optimization feature, so RLS policy might prevent index-scan,
  right now.
- RLS policy is not applied to the row to be inserted, or newer version
  of row to be updated. It can be implemented using before-row trigger.
  It might be an idea to inject RLS trigger function automatically, like
  FK constraints, but not yet.
- As Florian pointed out, current_user may change during query
  execution if DECLARE and FETCH are used.
  Although it is not a matter in RLS itself, should be improved later.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.v1.patch (75.9K, ../../CADyhKSWGtZqpsXtF7_q2FvKRvX6RqW+xv7VmxNmj4gubSBoo-g@mail.gmail.com/2-pgsql-v9.3-row-level-security.v1.patch)
  download | inline diff:
 doc/src/sgml/ref/alter_table.sgml          |   38 ++
 doc/src/sgml/user-manag.sgml               |  125 +++++++
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/dependency.c           |   22 ++
 src/backend/commands/explain.c             |    8 +-
 src/backend/commands/tablecmds.c           |  185 ++++++++++
 src/backend/optimizer/plan/planner.c       |    7 +
 src/backend/optimizer/prep/preptlist.c     |   54 ++-
 src/backend/optimizer/prep/prepunion.c     |   28 +-
 src/backend/optimizer/util/Makefile        |    2 +-
 src/backend/optimizer/util/rowlvsec.c      |  524 ++++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |   16 +
 src/backend/utils/adt/acl.c                |   34 ++
 src/backend/utils/adt/ri_triggers.c        |   33 +-
 src/backend/utils/cache/syscache.c         |   12 +
 src/include/catalog/dependency.h           |    1 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_proc.h              |    7 +
 src/include/catalog/pg_rowlvsec.h          |   39 +++
 src/include/commands/tablecmds.h           |    2 +
 src/include/nodes/parsenodes.h             |    5 +-
 src/include/optimizer/rowlvsec.h           |   31 ++
 src/include/utils/syscache.h               |    1 +
 src/test/regress/expected/rowlvsec.out     |  438 +++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |    3 +-
 src/test/regress/parallel_schedule         |    2 +-
 src/test/regress/serial_schedule           |    1 +
 src/test/regress/sql/rowlvsec.sql          |  216 ++++++++++++
 28 files changed, 1821 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 04e3e54..a3c89d1 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -806,6 +831,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..e4d5aaf 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,129 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition are available to fetch, update
+   or delete, except for a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can
+   contain references to columns of the relation, so it enables
+   to construct arbitrary rule to make access control decision
+   based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=# ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=# EXPLAIN (costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                                    QUERY PLAN
+-----------------------------------------------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ((("current_user"())::text = uname) OR has_superuser_privilege())
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and executed earlier than
+   user given conditions regardless to its cost.
+   On the other hand, these conditions are overwritten if current
+   user has superuser privilege.
+
+   Similary, <xref linkend="SQL-UPDATE"> or <xref linkend="SQL-DELETE">
+   commands are also restricted with this policy, as follows.
+<screen>
+postgres=# EXPLAIN (costs off) UPDATE customer SET upasswd = md5('abcd') WHERE cid = 2;
+                                       QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Index Scan using customer_pkey on customer
+               Index Cond: (cid = 2)
+               Filter: ((("current_user"())::text = uname) OR has_superuser_privilege())
+(5 rows)
+</screen>
+  </para>
+  <para>
+   Please note that row-level security policy of the parent
+   table is not applied to child relations. Scope of row-level
+   security policy is limited to the relation on which it is
+   set.
+<screen>
+postgres=# EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(y);
+                                QUERY PLAN
+--------------------------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: (((x % 2) = 0) OR has_superuser_privilege())
+         ->  Seq Scan on t2 t1
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3 t1
+                     Filter: (((x % 2) = 1) OR has_superuser_privilege())
+(12 rows)
+</screen>
+   In the above example, <literal>t1</literal> has inherited
+   child table <literal>t2</literal> and <literal>t3</literal>,
+   and row-level security policy is set on only <literal>t1</literal>,
+   and <literal>t3</literal>, not <literal>t2</literal>.
+
+   The row-level security policy of <literal>t1</literal>,
+   <literal>x</literal> must be even-number, is appended only
+   <literal>t1</literal>, neither <literal>t2</literal> nor
+   <literal>t3</literal>. On the contrary, <literal>t3</literal>
+   has different row-level security policy; <literal>x</literal>
+   must be odd-number.
+  </para>
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it is needed to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy.
+
+   We cannot contain sub-query as a part of row-level security
+   policy, so access control decision has to be done with
+   information originated from the target relation.
+
+   Row-level security policy implicitly added to reference for
+   the target relation may prevent query optimization, because
+   query optimization has to be done query execution of couse,
+   in spite of all the permission check shall be applied on
+   execution stage.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 62fc9b0..a21c116 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlvsec.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/dependency.c b/src/backend/catalog/dependency.c
index d4e1f76..73bb603 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlvsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -60,6 +61,7 @@
 #include "commands/proclang.h"
 #include "commands/schemacmds.h"
 #include "commands/seclabel.h"
+#include "commands/tablecmds.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
 #include "commands/typecmds.h"
@@ -1221,6 +1223,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLVSEC:
+			RemoveRowLvSecById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2269,6 +2275,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case ExtensionRelationId:
 			return OCLASS_EXTENSION;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLVSEC;
 	}
 
 	/* shouldn't get here */
@@ -2903,6 +2912,19 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLVSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("row-level security of %s"),
+								 relname);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 1e8f618..cdc7d3a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1825,8 +1825,12 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 		case T_TidScan:
 		case T_ForeignScan:
 		case T_ModifyTable:
-			/* Assert it's on a real relation */
-			Assert(rte->rtekind == RTE_RELATION);
+			/*
+			 * Assert it's on either a real relation, or a sub-query
+			 * originated from a real relation with row-level security
+			 */
+			Assert(rte->rtekind == RTE_RELATION ||
+				   (rte->rtekind == RTE_SUBQUERY && OidIsValid(rte->relid)));
 			objectname = get_rel_name(rte->relid);
 			if (es->verbose)
 				namespace = get_namespace_name(get_rel_namespace(rte->relid));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5c69cfb..6c94ca2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlvsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLvSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2834,6 +2836,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLvSecurity:
+			case AT_ResetRowLvSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3196,6 +3200,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLvSecurity:
+		case AT_ResetRowLvSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3471,6 +3477,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLvSecurity:
+		case AT_ResetRowLvSecurity:
+			ATExecSetRowLvSecurity(rel, (Node *) cmd->def);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7648,6 +7658,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLVSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+                         errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9745,6 +9771,165 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 	heap_close(relationRelation, RowExclusiveLock);
 }
 
+
+/*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR 
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLvSecurity(Relation relation, Node *clause)
+{
+	Oid			relationId = RelationGetRelid(relation);
+	Relation	rls_rel;
+
+	rls_rel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	if (clause)
+	{
+		/* SET ROW LEVEL SECURITY (<clause>) */
+		ParseState	   *pstate;
+		RangeTblEntry  *rte;
+		Node		   *qual;
+		HeapTuple		oldtup;
+		HeapTuple		newtup;
+		Datum			values[Natts_pg_rowlvsec];
+		bool			isnull[Natts_pg_rowlvsec];
+		bool			replace[Natts_pg_rowlvsec];
+		ObjectAddress	target;
+		ObjectAddress	myself;
+
+		memset(values,  0, sizeof(values));
+		memset(isnull,  0, sizeof(isnull));
+		memset(replace, 0, sizeof(replace));
+
+		/* Parse the supplied clause */
+		pstate = make_parsestate(NULL);
+
+		rte = addRangeTableEntryForRelation(pstate, relation,
+											NULL, false, false);
+		addRTEtoQuery(pstate, rte, false, true, true);
+
+		qual = transformWhereClause(pstate,
+									copyObject(clause),
+									"ROW LEVEL SECURITY");
+		/*
+		 * XXX - Likely, here is no technical reason why we don't support
+		 * subLinks in the security policy. But, we don't implement the
+		 * routines to support sub-queries in RLS policy.
+		 */
+		if (pstate->p_hasSubLinks)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("cannot use subquery in row-level security")));
+		/* No aggregate function support */
+		if (pstate->p_hasAggs)
+			ereport(ERROR,
+					(errcode(ERRCODE_GROUPING_ERROR),
+					 errmsg("cannot use aggregate in row-level security")));
+		/* No window function support */
+		if (pstate->p_hasWindowFuncs)
+			ereport(ERROR,
+					(errcode(ERRCODE_WINDOWING_ERROR),
+					 errmsg("cannot use window function in row-level security")));
+
+		/* Do insert or update pg_rowlvsec catalog */
+		oldtup = SearchSysCache1(RLSRELID, ObjectIdGetDatum(relationId));
+		if (HeapTupleIsValid(oldtup))
+		{
+			replace[Anum_pg_rowlvsec_rlsqual - 1] = true;
+			values[Anum_pg_rowlvsec_rlsqual - 1]
+				= CStringGetTextDatum(nodeToString(qual));
+
+			newtup = heap_modify_tuple(oldtup,
+									   RelationGetDescr(rls_rel),
+									   values, isnull, replace);
+			simple_heap_update(rls_rel, &newtup->t_self, newtup);
+
+			deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+									   relationId, false);
+			ReleaseSysCache(oldtup);
+		}
+		else
+		{
+			values[Anum_pg_rowlvsec_rlsrelid - 1]
+				= ObjectIdGetDatum(relationId);
+			values[Anum_pg_rowlvsec_rlsqual - 1]
+				= CStringGetTextDatum(nodeToString(qual));
+
+			newtup = heap_form_tuple(RelationGetDescr(rls_rel),
+									 values, isnull);
+			simple_heap_insert(rls_rel, newtup);
+
+		}
+		CatalogUpdateIndexes(rls_rel, newtup);
+
+		heap_freetuple(newtup);
+
+		/* records dependencies of RLS-policy and relation/columns */
+		target.classId = RelationRelationId;
+		target.objectId = relationId;
+		target.objectSubId = 0;
+
+		myself.classId = RowLevelSecurityRelationId;
+		myself.objectId = relationId;
+		myself.objectSubId = 0;
+
+		recordDependencyOn(&myself, &target, DEPENDENCY_NORMAL);
+
+		recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+							   DEPENDENCY_NORMAL);
+
+		free_parsestate(pstate);
+
+		CacheInvalidateRelcache(relation);
+	}
+	else
+	{
+		if (SearchSysCacheExists1(RLSRELID, ObjectIdGetDatum(relationId)))
+		{
+			ObjectAddress	address;
+
+			address.classId = RowLevelSecurityRelationId;
+			address.objectId = RelationGetRelid(relation);
+			address.objectSubId = 0;
+
+			performDeletion(&address, DROP_RESTRICT, 0);
+
+			CacheInvalidateRelcache(relation);
+		}
+		else
+		{
+			/* nothing to do here */
+			elog(INFO, "relation %s has no row-level security policy, skipped",
+				 RelationGetRelationName(relation));
+		}
+	}
+	heap_close(rls_rel, RowExclusiveLock);
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLvSecById(Oid relationId)
+{
+	Relation	rls_rel;
+	HeapTuple	tuple;
+
+	rls_rel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(RLSRELID, ObjectIdGetDatum(relationId));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for row-level security policy %u",
+			 relationId);
+
+	simple_heap_delete(rls_rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rls_rel, RowExclusiveLock);
+}
+
 /*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index df76341..f833e7f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -388,6 +388,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-level security policy of tables, if configured.
+	 * It has to be processed after inherited tables are expanded, because
+	 * row-level security policy has per-table basis.
+	 */
+	apply_rowlv_security(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 1af4e7f..8c42617 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -38,6 +38,44 @@
 static List *expand_targetlist(List *tlist, int command_type,
 				  Index result_relation, List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Vat that references sub-queries being originated from regular
+ * relations with row-level security policy.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry	*tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on the pseudo targetList", attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -62,7 +100,9 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	{
 		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
 
-		if (rte->subquery != NULL || rte->relid == InvalidOid)
+		if ((rte->subquery != NULL &&
+			 rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY) ||
+			rte->relid == InvalidOid)
 			elog(ERROR, "subquery cannot be result relation");
 	}
 
@@ -95,7 +135,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -111,7 +152,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -129,7 +171,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -298,7 +340,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														 result_relation,
+														 range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 6475633..415e841 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1696,7 +1696,33 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+											   context->root->parse->rtable);
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+				{
+					ListCell   *cell;
+
+					foreach (cell, rte->subquery->targetList)
+					{
+						TargetEntry	*subtle = lfirst(cell);
+						Var			*subvar;
+
+						if (IsA(subtle->expr, Const))
+							continue;
+
+						subvar = (Var *) subtle->expr;
+						if (var->varattno == subvar->varattno)
+						{
+							Assert(var->vartype == subvar->vartype);
+							Assert(var->vartypmod == subvar->vartypmod);
+							var->varattno = subtle->resno;
+						}
+					}
+				}
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..fe71c0c 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o rowlvsec.o tlist.o var.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlvsec.c b/src/backend/optimizer/util/rowlvsec.c
new file mode 100644
index 0000000..d311bdd
--- /dev/null
+++ b/src/backend/optimizer/util/rowlvsec.c
@@ -0,0 +1,524 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ */
+#include "postgres.h"
+
+#include "access/sysattr.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlvsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodes.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/primnodes.h"
+#include "optimizer/rowlvsec.h"
+#include "parser/parsetree.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/* hook to allow extensions to assign their own security policy */
+rowlv_security_hook_type rowlv_security_hook = NULL;
+
+/* current working mode of row-level security policy  */
+static RowLvSecMode rowlv_security_mode = RowLvSecEnabled;
+
+/*
+ * switch_rowlv_security_mode
+ *
+ * It switches the current performing mode of row-level security feature,
+ * and returns older mode. It enables to disable this functionality for
+ * temporary usage; such as foreign-key checks to prohibit update of PKs
+ * being referenced by others.
+ * The caller must ensure the saved older mode shall be restored, and
+ * it correctly works even if an error would be raised. A typical
+ * implementation encloses the code block with PG_TRY(), PG_CATCH() and
+ * PG_END_TRY() to catch errors and restore the saved mode prior to
+ * re-throw the error.
+ */
+RowLvSecMode
+switch_rowlv_security_mode(RowLvSecMode new_mode)
+{
+	RowLvSecMode	old_mode = rowlv_security_mode;
+
+	rowlv_security_mode = new_mode;
+
+	return old_mode;
+}
+
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/* This variable is not reference the target sub-query */
+		/* XXX - add comment here for inherited columns */
+
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List	   *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry	   *subtle = lfirst(cell);
+				Var			   *subvar;
+
+				if (IsA(subtle->expr, Const))
+					continue;
+
+				subvar = (Var *)subtle->expr;
+				Assert(IsA(subvar, Var));
+
+				if (var->varattno == subvar->varattno)
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with RLS
+ * policy but replaced to a sub-query. Here is no guarantee varattno fits
+ * with TargetEntry's resno of the sub-query, so needs to fix up them.
+ */
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context	context;
+	ListCell		   *cell;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy. 
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+
+	/*
+	 * Fixup translated_vars of AppendRelInfo
+	 */
+	foreach (cell, root->append_rel_list)
+	{
+		AppendRelInfo  *appinfo = lfirst(cell);
+		RangeTblEntry  *prte;
+		RangeTblEntry  *crte;
+
+		prte = rt_fetch(appinfo->parent_relid, root->parse->rtable);
+		crte = rt_fetch(appinfo->child_relid,  root->parse->rtable);
+
+		/*
+		 * Parent relation entry (that has rte->inh = true) is never
+		 * expanded, so no need to adject order of translated_vars.
+		 */
+		Assert(!(prte->rtekind == RTE_SUBQUERY &&
+				 prte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY));
+
+		/*
+		 * In case when the child relation entry is a sub-query originated
+		 * from a regular relation with RLS policy, attribute number to
+		 * reference a particular column (especially, system column) was
+		 * changed, thus, its correspondence relationship should be fixed.
+		 */
+		if (crte->rtekind == RTE_SUBQUERY &&
+			crte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			ListCell	   *l;
+			AttrNumber		attnum = 1;
+
+			/* Ensure to perform as a sub-query, not an inherited table */
+			appinfo->child_reltype = InvalidOid;
+
+			foreach (l, appinfo->translated_vars)
+			{
+				Var	   *var = lfirst(l);
+
+				if (var != NULL)
+				{
+					ListCell   *ll;
+
+					Assert(IsA(var, Var));
+
+					foreach (ll, crte->subquery->targetList)
+					{
+						TargetEntry	   *subtle = lfirst(ll);
+						Var			   *subvar;
+
+						/* skip references to dropped column */
+						if (IsA(subtle->expr, Const))
+							continue;
+
+						subvar = (Var *)subtle->expr;
+						Assert(IsA(subvar, Var));
+						/*
+						 * The "resno" of TargetEntry that has a reference to
+						 * the required column should be a corrent "varattno"
+						 * of Var node that reference a column from outside of
+						 * the sub-query.
+						 */
+						if (var->varattno == subvar->varattno)
+						{
+							var->varattno = subtle->resno;
+							break;
+						}
+					}
+					if (ll == NULL)
+						elog(ERROR, "invalid varattno %d", var->varattno);
+				}
+				attnum++;
+			}
+		}
+	}
+}
+
+/*
+ * make_pseudo_column
+ *
+ * It is a utility routine to generate a TargetEntry object that references
+ * a particular attribute of underlying relation. Its "expr" field shall
+ * have either Var or Const node. In case when the supplied "attnum" would
+ * be already dropped column, it injects a NULL value for a placeholder,
+ * according to the manner in preprocess_targetlist().
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	HeapTuple			atttup;
+	Form_pg_attribute	attform;
+	Expr   *expr;
+	char   *resname;
+
+	Assert(attnum != InvalidAttrNumber);
+
+	atttup = SearchSysCache2(ATTNUM,
+							 ObjectIdGetDatum(subrte->relid),
+							 Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(atttup))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, subrte->relid);
+	attform = (Form_pg_attribute) GETSTRUCT(atttup);
+
+	if (attform->attisdropped)
+	{
+		/* Insert NULL just for a placeholder for dropped column */
+		expr = (Expr *) makeConst(INT4OID,
+								  -1,
+								  InvalidOid,
+								  sizeof(int32),
+								  (Datum) 0,
+								  true,		/* isnull */
+								  true		/* byval */ );
+	}
+	else
+	{
+		expr = (Expr *) makeVar((Index) 1,
+								attform->attnum,
+								attform->atttypid,
+								attform->atttypmod,
+								InvalidOid,
+								0);
+	}
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(atttup);
+
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * pull_rowlv_security_policy
+ *
+ * This routine pulls an expression node from pg_rowlvsec system
+ * catalog, if configured. In addition to the configured qualifier,
+ * it also allows extensions to append its own row-level policy.
+ */
+static Expr *
+pull_rowlv_security_policy(PlannerInfo *root, RangeTblEntry *rte)
+{
+	HeapTuple	tuple;
+	Expr	   *qual_expr = NULL;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	tuple = SearchSysCache1(RLSRELID, ObjectIdGetDatum(rte->relid));
+	if (HeapTupleIsValid(tuple))
+	{
+		Datum	datum;
+		bool	isnull;
+		char   *sepol_string;
+		Expr   *super_expr;
+		Expr   *sepol_expr;
+
+		datum = SysCacheGetAttr(RLSRELID, tuple,
+								Anum_pg_rowlvsec_rlsqual, &isnull);
+		Assert(!isnull);
+
+		/*
+		 * XXX - Row-level security policy has the following form:
+		 *   (( user-supplied-condition ) OR has_superuser_privilege())
+		 * that allows superusers to bypass all the controls, and make
+		 * sure complete backup.
+		 */
+		sepol_string = TextDatumGetCString(datum);
+		sepol_expr = (Expr *) stringToNode(sepol_string);
+		Assert(exprType((Node *) sepol_expr) == BOOLOID);
+
+		super_expr = (Expr *) makeFuncExpr(F_HAS_SUPERUSER_PRIVILEGE,
+										   BOOLOID, NIL,
+										   InvalidOid,
+										   InvalidOid,
+										   COERCE_DONTCARE);
+		qual_expr = makeBoolExpr(OR_EXPR,
+								 list_make2(sepol_expr,
+											super_expr), -1);
+		pfree(sepol_string);
+
+		ReleaseSysCache(tuple);
+	}
+
+	/*
+	 * This hook allows extensitons to append its own row-level security
+	 * policy function. If qual_expr is not NULL, it should be connected
+	 * with AND operator.
+	 */
+	if (rowlv_security_hook)
+		qual_expr = (*rowlv_security_hook)(root, rte, qual_expr);
+
+	return qual_expr;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It replaces a particular RangeTblEntry to the relation with RLS policy
+ * by a simple sub-query that references the relation with WHERE clause
+ * to perform as row-level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Expr *qual_expr)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	HeapTuple		reltup;
+	Form_pg_class	relform;
+	AttrNumber		attno;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	/*
+	 * Construct a sub-query structure
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = copyObject(rte);
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+
+	subqry->rtable = list_make1(subrte);
+	subqry->jointree = makeFromExpr(list_make1(subrtr),
+									(Node *) qual_expr);
+
+	/*
+	 * Construct pseudo columns as TargetEntry of sub-query that reference
+	 * a particular attribute of the underlying relation.
+	 */
+	reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(subrte->relid));
+	if (!HeapTupleIsValid(reltup))
+		elog(ERROR, "cache lookup failed for relation %u", subrte->relid);
+	relform = (Form_pg_class) GETSTRUCT(reltup);
+
+	/*
+	 * XXX - the pseudo columns that just references regular attribute of 
+	 * underlying table must has identical resno with attnum of the column,
+	 * because preprocess_targetlist() sort-out target entries according to
+	 * the attribute number to fit physical structure of the target relation
+	 * in case when it has inherited tables.
+	 *
+	 * Likely, it can be optimized to have minimum number of TargetEntry
+	 * except for a case of the target relation of UPDATE. But not yet.
+	 */
+	for (attno = 1; attno <= relform->relnatts; attno++)
+	{
+		subtle = make_pseudo_column(subrte, attno);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(attno == subtle->resno);
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+
+	/*
+	 * Also, system columns and whole-row-reference are also added.
+	 */
+	for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++)
+	{
+		if (attno == ObjectIdAttributeNumber && !relform->relhasoids)
+			continue;
+
+		subtle = make_pseudo_column(subrte, attno);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+        colNameList = lappend(colNameList,
+                              makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+	ReleaseSysCache(reltup);
+
+	/*
+	 * Replace this RengeTblEntry by the sub-query with security-barrier
+	 */
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = subqry;
+	rte->security_barrier = true;
+
+	/* no permission check on subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+
+	rte->alias = NULL;
+	rte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+}
+
+/*
+ * apply_rowlv_security
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+apply_rowlv_security(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		needs_fixup = false;
+
+	/* Mode checks */
+	if (rowlv_security_mode == RowLvSecDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		Expr		   *qual_expr;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+		/*
+		 * Parent relation of inheritance tree is just a placeholder here,
+		 * so no need to apply row-level security them.
+		 */
+		if (rte->inh)
+			continue;
+
+		/*
+		 * It is unavailable to check rows to be inserted here,
+		 * before-row-insert trigger should be utilized instead.
+		 */
+		if (parse->commandType == CMD_INSERT &&
+			parse->resultRelation == rtindex)
+			continue;
+
+		/*
+		 * In case when row-level security policy was configured on the
+		 * table referenced by this RangeTblEntry, it shall be rewritten
+		 * to sub-query with the policy
+		 */
+		qual_expr = pull_rowlv_security_policy(root, rte);
+		if (qual_expr)
+		{
+			expand_rtentry_with_policy(root, rte, qual_expr);
+			needs_fixup = true;
+		}
+	}
+
+	/*
+	 * Since the relation with RLS policy was replaced by a sub-query,
+	 * thus resource number to reference a particular column can be
+	 * also moditifed. If we applied RLS policy on one or more relations,
+	 * varattno of Var node that has referenced the rewritten relation
+	 * needs to be fixed up.
+	 */
+	if (needs_fixup)
+		fixup_varattnos(root);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9eb1bed..e9c8d5b 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2058,6 +2058,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (clause) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLvSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLvSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 77322a1..bc34f1c 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -2863,6 +2863,40 @@ convert_column_priv_string(text *priv_type_text)
 	return convert_any_priv_string(priv_type_text, column_priv_map);
 }
 
+/*
+ * has_superuser_privilege
+ *     checks superuser privilege of current user
+ */
+Datum
+has_superuser_privilege(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_BOOL(superuser());
+}
+
+/*
+ * has_superuser_privilege_name
+ *     checks superuser privilege of the given username
+ */
+Datum
+has_superuser_privilege_name(PG_FUNCTION_ARGS)
+{
+	Name	username = PG_GETARG_NAME(0);
+	Oid		roleid = get_role_oid_or_public(NameStr(*username));
+
+	PG_RETURN_BOOL(superuser_arg(roleid));
+}
+
+/*
+ * has_superuser_privilege_id
+ *     checks superuser privilege of the given user-id
+ */
+Datum
+has_superuser_privilege_id(PG_FUNCTION_ARGS)
+{
+	Oid		roleid = PG_GETARG_OID(0);
+
+	PG_RETURN_BOOL(superuser_arg(roleid));
+}
 
 /*
  * has_database_privilege variants
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dd58f4e..69fa60a 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -35,10 +35,12 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_operator.h"
+#include "catalog/pg_rowlvsec.h"
 #include "catalog/pg_type.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "optimizer/rowlvsec.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
@@ -3341,6 +3343,7 @@ ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLvSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3424,11 +3427,31 @@ ri_PerformCheck(RI_QueryKey *qkey, SPIPlanPtr qplan,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
-	/* Finally we can run the query. */
-	spi_result = SPI_execute_snapshot(qplan,
-									  vals, nulls,
-									  test_snapshot, crosscheck_snapshot,
-									  false, false, limit);
+	/*
+	 * Switch row-level security model to prevent unexpected update or
+	 * delete of PKs being referenced by invisible tuples in FK-side.
+	 * However, we don't need to have special treatment towards INSERT
+	 * or UPDATE FKs that tries to reference invisible PKs.
+	 */
+	save_rls_mode = switch_rowlv_security_mode(query_rel == fk_rel
+											   ? RowLvSecDisabled
+											   : RowLvSecEnabled);
+	PG_TRY();
+	{
+		/* Finally we can run the query. */
+		spi_result = SPI_execute_snapshot(qplan,
+										  vals, nulls,
+										  test_snapshot,
+										  crosscheck_snapshot,
+										  false, false, limit);
+	}
+	PG_CATCH();
+	{
+		switch_rowlv_security_mode(save_rls_mode);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+	switch_rowlv_security_mode(save_rls_mode);
 
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index c365ec7..f34bfa5 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlvsec.h"
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_ts_config.h"
@@ -588,6 +589,17 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		1024
 	},
+	{RowLevelSecurityRelationId,	/* RLSRELID */
+		RowLevelSecurityIndexId,
+		1,
+		{
+			Anum_pg_rowlvsec_rlsrelid,
+			0,
+			0,
+			0
+		},
+		128
+	},
 	{RewriteRelationId,			/* RULERELNAME */
 		RewriteRelRulenameIndexId,
 		2,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f0eb564..37af71d 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -146,6 +146,7 @@ typedef enum ObjectClass
 	OCLASS_USER_MAPPING,		/* pg_user_mapping */
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
+	OCLASS_ROWLVSEC,			/* pg_rowlvsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 450ec25..6c0ca5e 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -306,6 +306,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlvsec_relid_index, 3839, on pg_rowlvsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId			3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 1e097dd..3d970fc 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3279,6 +3279,13 @@ DESCR("(internal)");
 DATA(insert OID = 2248 ( fmgr_sql_validator PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 2278 "26" _null_ _null_ _null_ _null_ fmgr_sql_validator _null_ _null_ _null_ ));
 DESCR("(internal)");
 
+DATA(insert OID = 3819 (  has_superuser_privilege	PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 16 "" _null_ _null_ _null_ _null_	has_superuser_privilege _null_ _null_ _null_ ));
+DESCR("superuser privilege of current user");
+DATA(insert OID = 3813 (  has_superuser_privilege	PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "19" _null_ _null_ _null_ _null_	has_superuser_privilege_name _null_ _null_ _null_ ));
+DESCR("superuser privilege by user name");
+DATA(insert OID = 3814 (  has_superuser_privilege	PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 16 "26" _null_ _null_ _null_ _null_	has_superuser_privilege_id _null_ _null_ _null_ ));
+DESCR("superuser privilege by user oid");
+
 DATA(insert OID = 2250 (  has_database_privilege		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 16 "19 25 25" _null_ _null_ _null_ _null_	has_database_privilege_name_name _null_ _null_ _null_ ));
 DESCR("user privilege on database by username, database name");
 DATA(insert OID = 2251 (  has_database_privilege		   PGNSP PGUID 12 1 0 0 0 f f f f t f s 3 0 16 "19 26 25" _null_ _null_ _null_ _null_	has_database_privilege_name_id _null_ _null_ _null_ ));
diff --git a/src/include/catalog/pg_rowlvsec.h b/src/include/catalog/pg_rowlvsec.h
new file mode 100644
index 0000000..629c024
--- /dev/null
+++ b/src/include/catalog/pg_rowlvsec.h
@@ -0,0 +1,39 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlvsec.h
+ *	  definition of the system row-level security policy relation (pg_rowlvsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_ROWLVSEC_H
+#define PG_ROWLVSEC_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *		pg_rowlvsec definition.  cpp turns this into
+ *		typedef struct FormData_pg_rowlvsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlvsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid			rlsrelid;	/* OID of the relation with RLS policy */
+#ifdef CATALOG_VARLEN
+	text		rlsqual;	/* Expression tree of the security policy */
+#endif
+} Form_pg_rowlvsec;
+
+/* ----------------
+ *		compiler constants for pg_rowlvsec
+ * ----------------
+ */
+#define Natts_pg_rowlvsec			2
+#define Anum_pg_rowlvsec_rlsrelid	1
+#define Anum_pg_rowlvsec_rlsqual	2
+
+#endif   /* PG_ROWLVSEC_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 9ceb086..66c02a9 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -40,6 +40,8 @@ extern void AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 							   Oid oldNspOid, Oid newNspOid,
 							   bool hasDependEntry);
 
+extern void RemoveRowLvSecById(Oid relationId);
+
 extern void CheckTableNotInUse(Relation rel, const char *stmt);
 
 extern void ExecuteTruncate(TruncateStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index deff1a3..69726e2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1227,6 +1228,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLvSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLvSecurity,		/* RESET ROW LEVEL SECURITY (...) */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/optimizer/rowlvsec.h b/src/include/optimizer/rowlvsec.h
new file mode 100644
index 0000000..92b912f
--- /dev/null
+++ b/src/include/optimizer/rowlvsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlvsec.h
+ *    prototypes for rowlvsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLVSEC_H
+#define ROWLVSEC_H
+
+#include "nodes/relation.h"
+
+typedef Expr *(*rowlv_security_hook_type)(PlannerInfo *root,
+										  RangeTblEntry *rte,
+										  Expr *qual_expr);
+extern PGDLLIMPORT rowlv_security_hook_type rowlv_security_hook;
+
+typedef enum
+{
+	RowLvSecEnabled,
+	RowLvSecDisabled,
+} RowLvSecMode;
+
+extern RowLvSecMode switch_rowlv_security_mode(RowLvSecMode new_mode);
+
+extern void apply_rowlv_security(PlannerInfo *root);
+
+#endif	/* ROWLVSEC_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index d59dd4e..4caf971 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -73,6 +73,7 @@ enum SysCacheIdentifier
 	RANGETYPE,
 	RELNAMENSP,
 	RELOID,
+	RLSRELID,
 	RULERELNAME,
 	STATRELATTINH,
 	TABLESPACEOID,
diff --git a/src/test/regress/expected/rowlvsec.out b/src/test/regress/expected/rowlvsec.out
new file mode 100644
index 0000000..9ae7454
--- /dev/null
+++ b/src/test/regress/expected/rowlvsec.out
@@ -0,0 +1,438 @@
+--
+-- Test 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 rowlvsec_user1;
+DROP USER IF EXISTS rowlvsec_user2;
+DROP USER IF EXISTS rowlvsec_user3;
+DROP SCHEMA IF EXISTS rowlvsec_schema CASCADE;
+RESET client_min_messages;
+-- Initial Setup
+CREATE USER rowlvsec_user0;
+CREATE USER rowlvsec_user1;
+CREATE USER rowlvsec_user2;
+CREATE USER rowlvsec_user3;
+CREATE SCHEMA rowlvsec_schema;
+GRANT ALL ON SCHEMA rowlvsec_schema TO public;
+SET search_path = rowlvsec_schema,public;
+CREATE TABLE customer (
+       cid      name primary key,
+       cname    text,
+       cage	int,
+       caddr	text
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "customer_pkey" for table "customer"
+GRANT SELECT ON TABLE customer TO public;
+INSERT INTO customer VALUES
+       ('rowlvsec_user1', 'alice', 18, 'USA'),
+       ('rowlvsec_user2', 'bob',   24, 'Japan'),
+       ('rowlvsec_user3', 'carol', 29, 'USA'),
+       ('rowlvsec_user4', 'dave',  32, 'Germany');
+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;
+-- XXX sub-query in RLS policy should be supported in the future
+CREATE FUNCTION customer_age() RETURNS int STABLE LANGUAGE sql
+       AS 'SELECT cage FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_age() TO public;
+CREATE FUNCTION customer_name() RETURNS text STABLE LANGUAGE sql
+       AS 'SELECT cname FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_age() TO public;
+CREATE FUNCTION customer_addr() RETURNS text STABLE LANGUAGE sql
+       AS 'SELECT caddr FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_addr() TO public;
+-- Create Test Data
+SET SESSION AUTHORIZATION rowlvsec_user0;
+CREATE TABLE drink (
+       id	   int primary key,
+       name	   text,
+       price	   int,
+       alcohol	   bool,
+       madein      text
+);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "drink_pkey" for table "drink"
+GRANT ALL ON drink TO public;
+INSERT INTO drink VALUES
+       (10, 'water',  100, false, 'Japan'),
+       (20, 'juice',  180, false, 'Germany'),
+       (30, 'coke',   120, false, 'USA'),
+       (40, 'coffee', 200, false, 'USA'),
+       (50, 'beer',   280, true,  'Germany'),
+       (60, 'wine',   340, true,  'USA'),
+       (70, 'sake',   300, true,  'Japan');
+CREATE TABLE purchase (
+       id          int references drink(id),
+       cname       text default customer_name(),
+       ymd         date
+);
+GRANT ALL ON purchase TO public;
+INSERT INTO purchase VALUES
+       (10, 'alice', '2012-05-03'),
+       (10, 'bob',   '2012-05-04'),
+       (10, 'alice', '2012-05-08'),
+       (20, 'alice', '2012-05-15'),
+       (20, 'bob',   '2012-05-18'),
+       (20, 'carol', '2012-05-21'),
+       (30, 'alice', '2012-05-28'),
+       (40, 'bob',   '2012-06-02'),
+       (60, 'carol', '2012-06-10');
+ALTER TABLE drink SET ROW LEVEL SECURITY (customer_age() > 19 OR NOT alcohol);
+ALTER TABLE purchase SET ROW LEVEL SECURITY (cname = customer_name());
+-- Viewpoint from Alice
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM drink WHERE f_leak(name);
+NOTICE:  f_leak => water
+NOTICE:  f_leak => juice
+NOTICE:  f_leak => coke
+NOTICE:  f_leak => coffee
+ id |  name  | price | alcohol | madein  
+----+--------+-------+---------+---------
+ 10 | water  |   100 | f       | Japan
+ 20 | juice  |   180 | f       | Germany
+ 30 | coke   |   120 | f       | USA
+ 40 | coffee |   200 | f       | USA
+(4 rows)
+
+SELECT * FROM purchase;
+ id | cname |    ymd     
+----+-------+------------
+ 10 | alice | 05-03-2012
+ 10 | alice | 05-08-2012
+ 20 | alice | 05-15-2012
+ 30 | alice | 05-28-2012
+(4 rows)
+
+SELECT * FROM drink NATURAL JOIN purchase;
+ id | name  | price | alcohol | madein  | cname |    ymd     
+----+-------+-------+---------+---------+-------+------------
+ 10 | water |   100 | f       | Japan   | alice | 05-03-2012
+ 10 | water |   100 | f       | Japan   | alice | 05-08-2012
+ 20 | juice |   180 | f       | Germany | alice | 05-15-2012
+ 30 | coke  |   120 | f       | USA     | alice | 05-28-2012
+(4 rows)
+
+-- Viewpoint from Bob
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink;
+ id |  name  | price | alcohol | madein  
+----+--------+-------+---------+---------
+ 10 | water  |   100 | f       | Japan
+ 20 | juice  |   180 | f       | Germany
+ 30 | coke   |   120 | f       | USA
+ 40 | coffee |   200 | f       | USA
+ 50 | beer   |   280 | t       | Germany
+ 60 | wine   |   340 | t       | USA
+ 70 | sake   |   300 | t       | Japan
+(7 rows)
+
+SELECT * FROM purchase WHERE f_leak(cname);
+NOTICE:  f_leak => bob
+NOTICE:  f_leak => bob
+NOTICE:  f_leak => bob
+ id | cname |    ymd     
+----+-------+------------
+ 10 | bob   | 05-04-2012
+ 20 | bob   | 05-18-2012
+ 40 | bob   | 06-02-2012
+(3 rows)
+
+SELECT * FROM drink NATURAL JOIN purchase;
+ id |  name  | price | alcohol | madein  | cname |    ymd     
+----+--------+-------+---------+---------+-------+------------
+ 10 | water  |   100 | f       | Japan   | bob   | 05-04-2012
+ 20 | juice  |   180 | f       | Germany | bob   | 05-18-2012
+ 40 | coffee |   200 | f       | USA     | bob   | 06-02-2012
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM drink WHERE f_leak(name);
+                                      QUERY PLAN                                       
+---------------------------------------------------------------------------------------
+ Subquery Scan on drink
+   Filter: f_leak(drink.name)
+   ->  Seq Scan on drink
+         Filter: ((customer_age() > 19) OR (NOT alcohol) OR has_superuser_privilege())
+(4 rows)
+
+EXPLAIN (costs off) SELECT * FROM purchase WHERE f_leak(cname);
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Subquery Scan on purchase
+   Filter: f_leak(purchase.cname)
+   ->  Seq Scan on purchase
+         Filter: ((cname = customer_name()) OR has_superuser_privilege())
+(4 rows)
+
+EXPLAIN (costs off) SELECT * FROM drink NATURAL JOIN purchase;
+                                            QUERY PLAN                                             
+---------------------------------------------------------------------------------------------------
+ Hash Join
+   Hash Cond: (rowlvsec_schema.purchase.id = drink.id)
+   ->  Seq Scan on purchase
+         Filter: ((cname = customer_name()) OR has_superuser_privilege())
+   ->  Hash
+         ->  Subquery Scan on drink
+               ->  Seq Scan on drink
+                     Filter: ((customer_age() > 19) OR (NOT alcohol) OR has_superuser_privilege())
+(8 rows)
+
+ALTER TABLE drink RESET ROW LEVEL SECURITY;  -- failed
+ERROR:  must be owner of relation drink
+ALTER TABLE drink SET ROW LEVEL SECURITY (customer_name() = 'bob'); -- failed
+ERROR:  must be owner of relation drink
+-- Change the security policy
+SET SESSION AUTHORIZATION rowlvsec_user0;
+ALTER TABLE drink SET ROW LEVEL SECURITY (madein = customer_addr());
+-- Viewpoint from Alice again
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM drink WHERE id > 30;
+ id |  name  | price | alcohol | madein 
+----+--------+-------+---------+--------
+ 40 | coffee |   200 | f       | USA
+ 60 | wine   |   340 | t       | USA
+(2 rows)
+
+-- Viewpoint from Bob again
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink WHERE f_leak(name);
+NOTICE:  f_leak => water
+NOTICE:  f_leak => sake
+ id | name  | price | alcohol | madein 
+----+-------+-------+---------+--------
+ 10 | water |   100 | f       | Japan
+ 70 | sake  |   300 | t       | Japan
+(2 rows)
+
+-- Writer Access and PK&FK constraints
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM drink NATURAL FULL OUTER JOIN purchase;
+ id |  name  | price | alcohol | madein | cname |    ymd     
+----+--------+-------+---------+--------+-------+------------
+ 10 |        |       |         |        | alice | 05-03-2012
+ 10 |        |       |         |        | alice | 05-08-2012
+ 20 |        |       |         |        | alice | 05-15-2012
+ 30 | coke   |   120 | f       | USA    | alice | 05-28-2012
+ 40 | coffee |   200 | f       | USA    |       | 
+ 60 | wine   |   340 | t       | USA    |       | 
+(6 rows)
+
+BEGIN;
+UPDATE purchase SET ymd = ymd + 1 RETURNING *;
+ id | cname |    ymd     
+----+-------+------------
+ 10 | alice | 05-04-2012
+ 10 | alice | 05-09-2012
+ 20 | alice | 05-16-2012
+ 30 | alice | 05-29-2012
+(4 rows)
+
+EXPLAIN (costs off) UPDATE purchase SET ymd = ymd + 1;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Update on purchase
+   ->  Subquery Scan on purchase
+         ->  Seq Scan on purchase
+               Filter: ((cname = customer_name()) OR has_superuser_privilege())
+(4 rows)
+
+ABORT;
+BEGIN;
+DELETE FROM purchase RETURNING *;
+ id | cname |    ymd     
+----+-------+------------
+ 10 | alice | 05-03-2012
+ 10 | alice | 05-08-2012
+ 20 | alice | 05-15-2012
+ 30 | alice | 05-28-2012
+(4 rows)
+
+EXPLAIN (costs off) DELETE FROM purchase;
+                                   QUERY PLAN                                   
+--------------------------------------------------------------------------------
+ Delete on purchase
+   ->  Subquery Scan on purchase
+         ->  Seq Scan on purchase
+               Filter: ((cname = customer_name()) OR has_superuser_privilege())
+(4 rows)
+
+ABORT;
+-- Failed to update PK row referenced by invisible FK
+UPDATE drink SET id = 100 WHERE id = 60;  -- fail
+ERROR:  update or delete on table "drink" violates foreign key constraint "purchase_id_fkey" on table "purchase"
+DETAIL:  Key (id)=(60) is still referenced from table "purchase".
+DELETE FROM drink WHERE id = 40;          -- fail
+ERROR:  update or delete on table "drink" violates foreign key constraint "purchase_id_fkey" on table "purchase"
+DETAIL:  Key (id)=(40) is still referenced from table "purchase".
+-- Failed to insert FK to reference invisible PK
+INSERT INTO purchase (id, ymd) VALUES (20, '2012-06-12');  -- fail
+ERROR:  insert or update on table "purchase" violates foreign key constraint "purchase_id_fkey"
+DETAIL:  Key (id)=(20) is not present in table "drink".
+INSERT INTO purchase (id, ymd) VALUES (30, '2012-06-12');  -- OK
+-- Same writter access from Bob
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink NATURAL FULL OUTER JOIN purchase;
+ id | name  | price | alcohol | madein | cname |    ymd     
+----+-------+-------+---------+--------+-------+------------
+ 10 | water |   100 | f       | Japan  | bob   | 05-04-2012
+ 20 |       |       |         |        | bob   | 05-18-2012
+ 40 |       |       |         |        | bob   | 06-02-2012
+ 70 | sake  |   300 | t       | Japan  |       | 
+(4 rows)
+
+BEGIN;
+UPDATE purchase SET ymd = ymd + 1 RETURNING *;
+ id | cname |    ymd     
+----+-------+------------
+ 10 | bob   | 05-05-2012
+ 20 | bob   | 05-19-2012
+ 40 | bob   | 06-03-2012
+(3 rows)
+
+ABORT;
+BEGIN;
+DELETE FROM purchase RETURNING *;
+ id | cname |    ymd     
+----+-------+------------
+ 10 | bob   | 05-04-2012
+ 20 | bob   | 05-18-2012
+ 40 | bob   | 06-02-2012
+(3 rows)
+
+ABORT;
+-- Table inheritance and RLS policy
+SET SESSION AUTHORIZATION rowlvsec_user0;
+CREATE TABLE t1 (a int, b text, c text);
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+INSERT INTO t1 VALUES
+       (1,'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'), ( 5,'eee'),
+       (6,'fff'), (7,'ggg'), (8,'hhh'), (9,'iii'), (10,'jjj');
+CREATE TABLE t2 (x int) INHERITS (t1);
+INSERT INTO t2 VALUES
+       (1,'abc'), (2,'bcd'), (3,'cde'), (4,'def'), ( 5,'efg'),
+       (6,'fgh'), (7,'ghi'), (8,'hij'), (9,'ijk'), (10,'jkl');
+CREATE TABLE t3 (y int) INHERITS (t1);
+INSERT INTO t3 VALUES (1,'xxx'),(2,'yyy'),(3,'zzz'),(4,'xyz');
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a     % 4 = customer_age() % 4);
+ALTER TABLE t2 SET ROW LEVEL SECURITY ((a+2) % 4 = customer_age() % 4);
+-- viewpoint from Alice
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM t1;
+ a  |  c  
+----+-----
+  2 | bbb
+  6 | fff
+ 10 | jjj
+  4 | def
+  8 | hij
+  1 | xxx
+  2 | yyy
+  3 | zzz
+  4 | xyz
+(9 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                                            QUERY PLAN                                             
+---------------------------------------------------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1
+                     Filter: (((a % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t1
+                     Filter: ((((a + 2) % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+         ->  Seq Scan on t3 t1
+(9 rows)
+
+BEGIN;
+UPDATE t1 SET c = c || '_updt';
+EXPLAIN (costs off) UPDATE t1 SET c = c || '_updt';
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Update on t1
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1
+               Filter: (((a % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t1
+               Filter: ((((a + 2) % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+   ->  Seq Scan on t3 t1
+(8 rows)
+
+ABORT;
+BEGIN;
+DELETE FROM t1;
+EXPLAIN (costs off) DELETE FROM t1;
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Delete on t1
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1
+               Filter: (((a % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t1
+               Filter: ((((a + 2) % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+   ->  Seq Scan on t3 t1
+(8 rows)
+
+ABORT;
+-- viewpoint from Carol
+SET SESSION AUTHORIZATION rowlvsec_user3;
+SELECT * FROM t1;
+ a |  c  
+---+-----
+ 1 | aaa
+ 5 | eee
+ 9 | iii
+ 3 | cde
+ 7 | ghi
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+ 4 | xyz
+(9 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                                            QUERY PLAN                                             
+---------------------------------------------------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1
+                     Filter: (((a % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t1
+                     Filter: ((((a + 2) % 4) = (customer_age() % 4)) OR has_superuser_privilege())
+         ->  Seq Scan on t3 t1
+(9 rows)
+
+BEGIN;
+UPDATE t1 SET c = c || '_updt';
+ABORT;
+BEGIN;
+DELETE FROM t1;
+ABORT;
+-- Clean up objects
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rowlvsec_schema CASCADE;
+NOTICE:  drop cascades to 14 other objects
+DETAIL:  drop cascades to table customer
+drop cascades to function f_leak(text)
+drop cascades to function customer_age()
+drop cascades to function customer_name()
+drop cascades to function customer_addr()
+drop cascades to table drink
+drop cascades to row-level security of drink
+drop cascades to table purchase
+drop cascades to row-level security of purchase
+drop cascades to table t1
+drop cascades to row-level security of t1
+drop cascades to table t2
+drop cascades to row-level security of t2
+drop cascades to table t3
+DROP USER rowlvsec_user3;
+DROP USER rowlvsec_user2;
+DROP USER rowlvsec_user1;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 7f560d2..2b4932d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -119,6 +119,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlvsec             | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -164,7 +165,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(153 rows)
+(154 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8852e0a..45a98c8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges security_label collate rowlvsec
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0bc5df7..a35dace 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlvsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlvsec.sql b/src/test/regress/sql/rowlvsec.sql
new file mode 100644
index 0000000..f7d72f6
--- /dev/null
+++ b/src/test/regress/sql/rowlvsec.sql
@@ -0,0 +1,216 @@
+--
+-- Test 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 rowlvsec_user1;
+DROP USER IF EXISTS rowlvsec_user2;
+DROP USER IF EXISTS rowlvsec_user3;
+
+DROP SCHEMA IF EXISTS rowlvsec_schema CASCADE;
+
+RESET client_min_messages;
+
+-- Initial Setup
+CREATE USER rowlvsec_user0;
+CREATE USER rowlvsec_user1;
+CREATE USER rowlvsec_user2;
+CREATE USER rowlvsec_user3;
+
+CREATE SCHEMA rowlvsec_schema;
+GRANT ALL ON SCHEMA rowlvsec_schema TO public;
+SET search_path = rowlvsec_schema,public;
+
+CREATE TABLE customer (
+       cid      name primary key,
+       cname    text,
+       cage	int,
+       caddr	text
+);
+GRANT SELECT ON TABLE customer TO public;
+INSERT INTO customer VALUES
+       ('rowlvsec_user1', 'alice', 18, 'USA'),
+       ('rowlvsec_user2', 'bob',   24, 'Japan'),
+       ('rowlvsec_user3', 'carol', 29, 'USA'),
+       ('rowlvsec_user4', 'dave',  32, 'Germany');
+
+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;
+
+-- XXX sub-query in RLS policy should be supported in the future
+CREATE FUNCTION customer_age() RETURNS int STABLE LANGUAGE sql
+       AS 'SELECT cage FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_age() TO public;
+
+CREATE FUNCTION customer_name() RETURNS text STABLE LANGUAGE sql
+       AS 'SELECT cname FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_age() TO public;
+
+CREATE FUNCTION customer_addr() RETURNS text STABLE LANGUAGE sql
+       AS 'SELECT caddr FROM customer WHERE cid = session_user';
+GRANT EXECUTE ON FUNCTION customer_addr() TO public;
+
+-- Create Test Data
+SET SESSION AUTHORIZATION rowlvsec_user0;
+
+CREATE TABLE drink (
+       id	   int primary key,
+       name	   text,
+       price	   int,
+       alcohol	   bool,
+       madein      text
+);
+GRANT ALL ON drink TO public;
+INSERT INTO drink VALUES
+       (10, 'water',  100, false, 'Japan'),
+       (20, 'juice',  180, false, 'Germany'),
+       (30, 'coke',   120, false, 'USA'),
+       (40, 'coffee', 200, false, 'USA'),
+       (50, 'beer',   280, true,  'Germany'),
+       (60, 'wine',   340, true,  'USA'),
+       (70, 'sake',   300, true,  'Japan');
+
+CREATE TABLE purchase (
+       id          int references drink(id),
+       cname       text default customer_name(),
+       ymd         date
+);
+GRANT ALL ON purchase TO public;
+INSERT INTO purchase VALUES
+       (10, 'alice', '2012-05-03'),
+       (10, 'bob',   '2012-05-04'),
+       (10, 'alice', '2012-05-08'),
+       (20, 'alice', '2012-05-15'),
+       (20, 'bob',   '2012-05-18'),
+       (20, 'carol', '2012-05-21'),
+       (30, 'alice', '2012-05-28'),
+       (40, 'bob',   '2012-06-02'),
+       (60, 'carol', '2012-06-10');
+
+ALTER TABLE drink SET ROW LEVEL SECURITY (customer_age() > 19 OR NOT alcohol);
+ALTER TABLE purchase SET ROW LEVEL SECURITY (cname = customer_name());
+
+-- Viewpoint from Alice
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM drink WHERE f_leak(name);
+SELECT * FROM purchase;
+SELECT * FROM drink NATURAL JOIN purchase;
+
+-- Viewpoint from Bob
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink;
+SELECT * FROM purchase WHERE f_leak(cname);
+SELECT * FROM drink NATURAL JOIN purchase;
+
+EXPLAIN (costs off) SELECT * FROM drink WHERE f_leak(name);
+EXPLAIN (costs off) SELECT * FROM purchase WHERE f_leak(cname);
+EXPLAIN (costs off) SELECT * FROM drink NATURAL JOIN purchase;
+
+ALTER TABLE drink RESET ROW LEVEL SECURITY;  -- failed
+ALTER TABLE drink SET ROW LEVEL SECURITY (customer_name() = 'bob'); -- failed
+
+-- Change the security policy
+SET SESSION AUTHORIZATION rowlvsec_user0;
+ALTER TABLE drink SET ROW LEVEL SECURITY (madein = customer_addr());
+
+-- Viewpoint from Alice again
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM drink WHERE id > 30;
+
+-- Viewpoint from Bob again
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink WHERE f_leak(name);
+
+-- Writer Access and PK&FK constraints
+SET SESSION AUTHORIZATION rowlvsec_user1;
+
+SELECT * FROM drink NATURAL FULL OUTER JOIN purchase;
+
+BEGIN;
+UPDATE purchase SET ymd = ymd + 1 RETURNING *;
+EXPLAIN (costs off) UPDATE purchase SET ymd = ymd + 1;
+ABORT;
+
+BEGIN;
+DELETE FROM purchase RETURNING *;
+EXPLAIN (costs off) DELETE FROM purchase;
+ABORT;
+
+-- Failed to update PK row referenced by invisible FK
+UPDATE drink SET id = 100 WHERE id = 60;  -- fail
+DELETE FROM drink WHERE id = 40;          -- fail
+
+-- Failed to insert FK to reference invisible PK
+INSERT INTO purchase (id, ymd) VALUES (20, '2012-06-12');  -- fail
+INSERT INTO purchase (id, ymd) VALUES (30, '2012-06-12');  -- OK
+
+-- Same writter access from Bob
+SET SESSION AUTHORIZATION rowlvsec_user2;
+SELECT * FROM drink NATURAL FULL OUTER JOIN purchase;
+
+BEGIN;
+UPDATE purchase SET ymd = ymd + 1 RETURNING *;
+ABORT;
+
+BEGIN;
+DELETE FROM purchase RETURNING *;
+ABORT;
+
+-- Table inheritance and RLS policy
+SET SESSION AUTHORIZATION rowlvsec_user0;
+
+CREATE TABLE t1 (a int, b text, c text);
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+
+INSERT INTO t1 VALUES
+       (1,'aaa'), (2,'bbb'), (3,'ccc'), (4,'ddd'), ( 5,'eee'),
+       (6,'fff'), (7,'ggg'), (8,'hhh'), (9,'iii'), (10,'jjj');
+CREATE TABLE t2 (x int) INHERITS (t1);
+INSERT INTO t2 VALUES
+       (1,'abc'), (2,'bcd'), (3,'cde'), (4,'def'), ( 5,'efg'),
+       (6,'fgh'), (7,'ghi'), (8,'hij'), (9,'ijk'), (10,'jkl');
+CREATE TABLE t3 (y int) INHERITS (t1);
+INSERT INTO t3 VALUES (1,'xxx'),(2,'yyy'),(3,'zzz'),(4,'xyz');
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a     % 4 = customer_age() % 4);
+ALTER TABLE t2 SET ROW LEVEL SECURITY ((a+2) % 4 = customer_age() % 4);
+
+-- viewpoint from Alice
+SET SESSION AUTHORIZATION rowlvsec_user1;
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+BEGIN;
+UPDATE t1 SET c = c || '_updt';
+EXPLAIN (costs off) UPDATE t1 SET c = c || '_updt';
+ABORT;
+BEGIN;
+DELETE FROM t1;
+EXPLAIN (costs off) DELETE FROM t1;
+ABORT;
+
+-- viewpoint from Carol
+SET SESSION AUTHORIZATION rowlvsec_user3;
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+BEGIN;
+UPDATE t1 SET c = c || '_updt';
+ABORT;
+BEGIN;
+DELETE FROM t1;
+ABORT;
+
+-- Clean up objects
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rowlvsec_schema CASCADE;
+DROP USER rowlvsec_user3;
+DROP USER rowlvsec_user2;
+DROP USER rowlvsec_user1;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-06-26 16:31 ` Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2012-06-26 16:31 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Thu, Jun 14, 2012 at 11:43 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> In the previous discussion, we planned to add a syntax option to
> clarify the command type to fire the RLS policy, such as FOR UPDATE.
> But current my opinion is, it is not necessary. For example, we can
> reference the contents of rows being updated / deleted using
> RETURNING clause. So, it does not make sense to have different
> RLS policy at UPDATE / DELETE from SELECT.

I agree.  That doesn't make sense, and we don't need to support it.

> On the other hand, I'm not 100% sure about my design to restrict
> rows to be updated and deleted. Similarly, it expands the target
> relation of UPDATE or DELETE statement into a sub-query with
> condition. ExecModifyTable() pulls a tuple from the sub-query,
> instead of regular table, so it seems to me working at least, but
> I didn't try all the possible cases of course.

I don't think there's any reason why that shouldn't work.  DELETE ..
USING already allows ModifyTable to be scanning a join product, so
this is not much different.

> Of course, here is some limitations, to keep the patch size reasonable
> level to review.
> - The permission to bypass RLS policy was under discussion.
>  If and when we should provide a special permission to bypass RLS
>  policy, the "OR has_superuser_privilege()" shall be replaced by
>  "OR has_table_privilege(tableoid, 'RLSBYPASS')".

I think you're missing the point.  Everyone who has commented on this
issue is in favor of having some check that causes the RLS predicate
*not to get added in the first place*.  Adding a modified predicate is
not the same thing.  First, the query planner might not be smart
enough to optimize away the clause even when the predicate holds,
causing an unnecessary performance drain.  Second, there's too much
danger of being able to set a booby-trap for the superuser this way.
Suppose that the RLS subsystem replaces f_malicious() by f_malicious
OR has_superuser_privilege().  Now the superuser comes along with the
nightly pg_dump run and the query optimizer executes SELECT * FROM
nuts WHERE f_malicious() OR has_superuser_privilege().  The query
optimizer notes that the cost of f_malicious() is very low and decides
to execute that before has_superuser_privilege().  Oops.  I think it's
just not acceptable to handle this by clause-munging: we need to not
add the clause in the first place.

Comments on the patch itself:

1. Please do not abbreviate rowlevel to rowlv or RowLevel to RowLv or
ROWLEVEL to ROWLV.  That makes it harder to read and harder to grep.
Spell it out.

2. Since the entirety of ATExecSetRowLvSecurity is conditional on
whether clause != NULL, you might as well split it into two functions,
one for each case.

3. The fact that you've had to hack preprocess_targetlist and
adjust_appendrel_attrs_mutator suggests to me that the insertion of
the generate subquery is happening at the wrong phase of the process.
We don't need those special cases for views, so it seems like we
shouldn't need them here, either.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2012-06-26 20:36   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-26 20:36 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/6/26 Robert Haas <robertmhaas@gmail.com>:
>> Of course, here is some limitations, to keep the patch size reasonable
>> level to review.
>> - The permission to bypass RLS policy was under discussion.
>>  If and when we should provide a special permission to bypass RLS
>>  policy, the "OR has_superuser_privilege()" shall be replaced by
>>  "OR has_table_privilege(tableoid, 'RLSBYPASS')".
>
> I think you're missing the point.  Everyone who has commented on this
> issue is in favor of having some check that causes the RLS predicate
> *not to get added in the first place*.  Adding a modified predicate is
> not the same thing.  First, the query planner might not be smart
> enough to optimize away the clause even when the predicate holds,
> causing an unnecessary performance drain.  Second, there's too much
> danger of being able to set a booby-trap for the superuser this way.
> Suppose that the RLS subsystem replaces f_malicious() by f_malicious
> OR has_superuser_privilege().  Now the superuser comes along with the
> nightly pg_dump run and the query optimizer executes SELECT * FROM
> nuts WHERE f_malicious() OR has_superuser_privilege().  The query
> optimizer notes that the cost of f_malicious() is very low and decides
> to execute that before has_superuser_privilege().  Oops.  I think it's
> just not acceptable to handle this by clause-munging: we need to not
> add the clause in the first place.
>
Here is a simple idea to avoid the second problematic scenario; that
assign 0 as cost of has_superuser_privilege(). I allows to keep this
function more lightweight than any possible malicious function, since
CreateFunction enforces positive value.

But the first point is still remaining.

As you pointed out before, it might be a solution to have case-handling
for superusers and others in case of simple query protocol; that uses
same snapshot for planner and executor stage.

How should we handle the issue?

During the previous discussion, Tom mentioned about an idea that
saves prepared statement hashed with user-id to switch the query
plan depending on user's privilege.
Even though I hesitated to buy this idea at that time, it might be
worth to investigate this idea to satisfy both security and performance;
that will generate multiple query plans to be chosen at executor
stage later.

How about your opinion?

> Comments on the patch itself:
>
> 1. Please do not abbreviate rowlevel to rowlv or RowLevel to RowLv or
> ROWLEVEL to ROWLV.  That makes it harder to read and harder to grep.
> Spell it out.
>
OK,

> 2. Since the entirety of ATExecSetRowLvSecurity is conditional on
> whether clause != NULL, you might as well split it into two functions,
> one for each case.
>
OK,

> 3. The fact that you've had to hack preprocess_targetlist and
> adjust_appendrel_attrs_mutator suggests to me that the insertion of
> the generate subquery is happening at the wrong phase of the process.
> We don't need those special cases for views, so it seems like we
> shouldn't need them here, either.
>
Main reason why I had to patch them is special case handling for
references to system columns; that is unavailable to have for sub-
queries.
But, I'm not 100% sure around these implementation. So, it needs
more investigations.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-06-26 20:59     ` Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Tom Lane @ 2012-06-26 20:59 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
> 2012/6/26 Robert Haas <robertmhaas@gmail.com>:
>> I think you're missing the point.  Everyone who has commented on this
>> issue is in favor of having some check that causes the RLS predicate
>> *not to get added in the first place*.

> Here is a simple idea to avoid the second problematic scenario; that
> assign 0 as cost of has_superuser_privilege().

I am not sure which part of "this isn't safe" isn't getting through to
you.  Aside from the scenarios Robert mentioned, consider the
possibility that f_malicious() is marked immutable, so that the planner
is likely to call it (to replace the call with its value) before it will
ever think about whether has_superuser_privilege should be called first.

Please just do what everybody is asking for, and create a bypass that
does not require fragile, easily-broken-by-future-changes assumptions
about what the planner will do with a WHERE clause.

			regards, tom lane



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
@ 2012-06-27 05:18       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-27 05:18 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/6/26 Tom Lane <tgl@sss.pgh.pa.us>:
> Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
>> 2012/6/26 Robert Haas <robertmhaas@gmail.com>:
>>> I think you're missing the point.  Everyone who has commented on this
>>> issue is in favor of having some check that causes the RLS predicate
>>> *not to get added in the first place*.
>
>> Here is a simple idea to avoid the second problematic scenario; that
>> assign 0 as cost of has_superuser_privilege().
>
> I am not sure which part of "this isn't safe" isn't getting through to
> you.  Aside from the scenarios Robert mentioned, consider the
> possibility that f_malicious() is marked immutable, so that the planner
> is likely to call it (to replace the call with its value) before it will
> ever think about whether has_superuser_privilege should be called first.
>
> Please just do what everybody is asking for, and create a bypass that
> does not require fragile, easily-broken-by-future-changes assumptions
> about what the planner will do with a WHERE clause.
>
The problem is the way to implement it.
If we would have permission checks on planner stage, it cannot handle
a case when user-id would be switched prior to executor stage, thus
it needs something remedy to handle the scenario correctly.
Instead of a unique plan per query, it might be a solution to generate
multiple plans depending on user-id, and choose a proper one in
executor stage.

Which type of implementation is what everybody is asking for?

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-06-27 11:21         ` Florian Pflug <fgp@phlo.org>
  2012-06-27 12:23           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Florian Pflug @ 2012-06-27 11:21 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

On Jun27, 2012, at 07:18 , Kohei KaiGai wrote:
> The problem is the way to implement it.
> If we would have permission checks on planner stage, it cannot handle
> a case when user-id would be switched prior to executor stage, thus
> it needs something remedy to handle the scenario correctly.
> Instead of a unique plan per query, it might be a solution to generate
> multiple plans depending on user-id, and choose a proper one in
> executor stage.
> 
> Which type of implementation is what everybody is asking for?

I think you need to

 a) Determine the user-id at planning time, and insert the matching
    RLS clause

b1) Either re-plan the query if the user-id changes between planning
    and execution time, which means making the user-id a part of the
    plan-cache key.

b2) Or decree that for RLS purposes, it's the user-id at planning time,
    not execution time, that counts.

best regards,
Florian Pflug




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
@ 2012-06-27 12:23           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-27 12:23 UTC (permalink / raw)
  To: Florian Pflug <fgp@phlo.org>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

2012/6/27 Florian Pflug <fgp@phlo.org>:
> On Jun27, 2012, at 07:18 , Kohei KaiGai wrote:
>> The problem is the way to implement it.
>> If we would have permission checks on planner stage, it cannot handle
>> a case when user-id would be switched prior to executor stage, thus
>> it needs something remedy to handle the scenario correctly.
>> Instead of a unique plan per query, it might be a solution to generate
>> multiple plans depending on user-id, and choose a proper one in
>> executor stage.
>>
>> Which type of implementation is what everybody is asking for?
>
> I think you need to
>
>  a) Determine the user-id at planning time, and insert the matching
>    RLS clause
>
> b1) Either re-plan the query if the user-id changes between planning
>    and execution time, which means making the user-id a part of the
>    plan-cache key.
>
> b2) Or decree that for RLS purposes, it's the user-id at planning time,
>    not execution time, that counts.
>
My preference is b1, because b2 approach takes user visible changes
in concepts of permission checks.

Probably, plan-cache should be also invalidated when user's property
was modified or grant/revoke is issued, in addition to the table itself.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
@ 2012-06-27 12:42           ` Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Robert Haas @ 2012-06-27 12:42 UTC (permalink / raw)
  To: Florian Pflug <fgp@phlo.org>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers

On Wed, Jun 27, 2012 at 7:21 AM, Florian Pflug <fgp@phlo.org> wrote:
> On Jun27, 2012, at 07:18 , Kohei KaiGai wrote:
>> The problem is the way to implement it.
>> If we would have permission checks on planner stage, it cannot handle
>> a case when user-id would be switched prior to executor stage, thus
>> it needs something remedy to handle the scenario correctly.
>> Instead of a unique plan per query, it might be a solution to generate
>> multiple plans depending on user-id, and choose a proper one in
>> executor stage.
>>
>> Which type of implementation is what everybody is asking for?
>
> I think you need to
>
>  a) Determine the user-id at planning time, and insert the matching
>    RLS clause
>
> b1) Either re-plan the query if the user-id changes between planning
>    and execution time, which means making the user-id a part of the
>    plan-cache key.
>
> b2) Or decree that for RLS purposes, it's the user-id at planning time,
>    not execution time, that counts.

Or b3, flag plans that depend on the user ID inside the plan-cache,
and just flush all of those (but only those) when the user ID changes.
 In the common case where RLS is not used, that might ease the sting.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2012-06-27 13:07             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-27 13:07 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Florian Pflug <fgp@phlo.org>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers

2012/6/27 Robert Haas <robertmhaas@gmail.com>:
> On Wed, Jun 27, 2012 at 7:21 AM, Florian Pflug <fgp@phlo.org> wrote:
>> On Jun27, 2012, at 07:18 , Kohei KaiGai wrote:
>>> The problem is the way to implement it.
>>> If we would have permission checks on planner stage, it cannot handle
>>> a case when user-id would be switched prior to executor stage, thus
>>> it needs something remedy to handle the scenario correctly.
>>> Instead of a unique plan per query, it might be a solution to generate
>>> multiple plans depending on user-id, and choose a proper one in
>>> executor stage.
>>>
>>> Which type of implementation is what everybody is asking for?
>>
>> I think you need to
>>
>>  a) Determine the user-id at planning time, and insert the matching
>>    RLS clause
>>
>> b1) Either re-plan the query if the user-id changes between planning
>>    and execution time, which means making the user-id a part of the
>>    plan-cache key.
>>
>> b2) Or decree that for RLS purposes, it's the user-id at planning time,
>>    not execution time, that counts.
>
> Or b3, flag plans that depend on the user ID inside the plan-cache,
> and just flush all of those (but only those) when the user ID changes.
>  In the common case where RLS is not used, that might ease the sting.
>
Probably, PlannedStmt->invalItems allows to handle invalidation of
plan-cache without big code changes. I'll try to put a flag of user-id
to track the query plan with RLS assumed, or InvalidOid if no RLS
was applied in this plan.
I'll investigate the implementation for more details.

Do we have any other scenario that run a query plan under different
user privilege rather than planner stage?

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-06-27 15:58               ` Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Florian Pflug @ 2012-06-27 15:58 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers

On Jun27, 2012, at 15:07 , Kohei KaiGai wrote:
> Probably, PlannedStmt->invalItems allows to handle invalidation of
> plan-cache without big code changes. I'll try to put a flag of user-id
> to track the query plan with RLS assumed, or InvalidOid if no RLS
> was applied in this plan.
> I'll investigate the implementation for more details.
> 
> Do we have any other scenario that run a query plan under different
> user privilege rather than planner stage?

Hm, what happens if a SECURITY DEFINER functions returns a refcursor?

Actually, I wonder how we handle that today. If the executor is
responsible for permission checks, that wouldn't we apply the calling
function's privilege level in that case, at least of the cursor isn't
fetched from in the SECURITY DEFINER function? If I find some time,
I'll check...

best regards,
Florian Pflug




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
@ 2012-06-28 15:02                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-06-28 15:02 UTC (permalink / raw)
  To: Florian Pflug <fgp@phlo.org>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; pgsql-hackers

2012/6/27 Florian Pflug <fgp@phlo.org>:
> On Jun27, 2012, at 15:07 , Kohei KaiGai wrote:
>> Probably, PlannedStmt->invalItems allows to handle invalidation of
>> plan-cache without big code changes. I'll try to put a flag of user-id
>> to track the query plan with RLS assumed, or InvalidOid if no RLS
>> was applied in this plan.
>> I'll investigate the implementation for more details.
>>
>> Do we have any other scenario that run a query plan under different
>> user privilege rather than planner stage?
>
> Hm, what happens if a SECURITY DEFINER functions returns a refcursor?
>
> Actually, I wonder how we handle that today. If the executor is
> responsible for permission checks, that wouldn't we apply the calling
> function's privilege level in that case, at least of the cursor isn't
> fetched from in the SECURITY DEFINER function? If I find some time,
> I'll check...
>
My impression is, here is no matter even if SECURITY DEFINER function
returns refcursor.

A SECURITY DEFINER function (or Trusted Procedure on sepgsql, or
Set-UID program on Linux) provides unprivileged users a particular
"limited way" to access protected data. It means owner of the security
definer function admits it is reasonable to show the protected data
as long as unprivileged users access them via the function.

It is same reason why we admit view's access for users who have
privileges on views but unprivileged to underlying tables.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-06-28 15:29                   ` Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-28 15:34                     ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 2 replies; 165+ messages in thread

From: Tom Lane @ 2012-06-28 15:29 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Florian Pflug <fgp@phlo.org>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
> 2012/6/27 Florian Pflug <fgp@phlo.org>:
>> Hm, what happens if a SECURITY DEFINER functions returns a refcursor?

> My impression is, here is no matter even if SECURITY DEFINER function
> returns refcursor.

I think Florian has a point: it *should* work, but *will* it?

I believe it works today, because the executor only applies permissions
checks during query startup.  So those checks are executed while still
within the SECURITY DEFINER context, and should behave as expected.
Subsequently, the cursor portal is returned to caller and caller can
execute it to completion, no problem.

However, with RLS security-related checks will happen throughout the
execution of the portal.  They might do the wrong thing once the
SECURITY DEFINER function has been exited.

We might need to consider that a portal has a local value of
"current_user", which is kind of a pain, but probably doable.

			regards, tom lane



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
@ 2012-06-28 15:34                     ` Florian Pflug <fgp@phlo.org>
  2012-06-28 15:46                       ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 165+ messages in thread

From: Florian Pflug @ 2012-06-28 15:34 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

On Jun28, 2012, at 17:29 , Tom Lane wrote:
> Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
>> 2012/6/27 Florian Pflug <fgp@phlo.org>:
>>> Hm, what happens if a SECURITY DEFINER functions returns a refcursor?
> 
>> My impression is, here is no matter even if SECURITY DEFINER function
>> returns refcursor.
> 
> I think Florian has a point: it *should* work, but *will* it?
> 
> I believe it works today, because the executor only applies permissions
> checks during query startup.  So those checks are executed while still
> within the SECURITY DEFINER context, and should behave as expected.
> Subsequently, the cursor portal is returned to caller and caller can
> execute it to completion, no problem.

Don't we (sometimes?) defer query startup to the first time FETCH is
called?

best regards,
Florian Pflug




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-28 15:34                     ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
@ 2012-06-28 15:46                       ` Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 0 replies; 165+ messages in thread

From: Tom Lane @ 2012-06-28 15:46 UTC (permalink / raw)
  To: Florian Pflug <fgp@phlo.org>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

Florian Pflug <fgp@phlo.org> writes:
> On Jun28, 2012, at 17:29 , Tom Lane wrote:
>> I believe it works today, because the executor only applies permissions
>> checks during query startup.  So those checks are executed while still
>> within the SECURITY DEFINER context, and should behave as expected.
>> Subsequently, the cursor portal is returned to caller and caller can
>> execute it to completion, no problem.

> Don't we (sometimes?) defer query startup to the first time FETCH is
> called?

There are things inside individual plan node functions that may only
happen when the first row is demanded, but permissions checks are done
in ExecutorStart().

			regards, tom lane



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
@ 2012-07-01 14:53                     ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-07-01 14:53 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Florian Pflug <fgp@phlo.org>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

2012/6/28 Tom Lane <tgl@sss.pgh.pa.us>:
> Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
>> 2012/6/27 Florian Pflug <fgp@phlo.org>:
>>> Hm, what happens if a SECURITY DEFINER functions returns a refcursor?
>
>> My impression is, here is no matter even if SECURITY DEFINER function
>> returns refcursor.
>
> I think Florian has a point: it *should* work, but *will* it?
>
> I believe it works today, because the executor only applies permissions
> checks during query startup.  So those checks are executed while still
> within the SECURITY DEFINER context, and should behave as expected.
> Subsequently, the cursor portal is returned to caller and caller can
> execute it to completion, no problem.
>
> However, with RLS security-related checks will happen throughout the
> execution of the portal.  They might do the wrong thing once the
> SECURITY DEFINER function has been exited.
>
I tried the scenario that Florian pointed out.

postgres=# CREATE OR REPLACE FUNCTION f_test(refcursor) RETURNS refcursor
postgres-# SECURITY DEFINER LANGUAGE plpgsql
postgres-# AS 'BEGIN OPEN $1 FOR SELECT current_user, * FROM t1;
RETURN $1; END';
CREATE FUNCTION
postgres=# BEGIN;
BEGIN
postgres=# SELECT f_test('abc');
 f_test
--------
 abc
(1 row)

postgres=# FETCH abc;
 current_user | a |  b
--------------+---+-----
 kaigai       | 1 | aaa
(1 row)

postgres=# SET SESSION AUTHORIZATION alice;
SET
postgres=> FETCH abc;
 current_user | a |  b
--------------+---+-----
 alice        | 2 | bbb
(1 row)

postgres=> SET SESSION AUTHORIZATION bob;
SET
postgres=> FETCH abc;
 current_user | a |  b
--------------+---+-----
 bob          | 3 | ccc
(1 row)

Indeed, the output of "current_user" depends on the setting of session
authorization, even though it was declared within security definer
functions (thus, its security checks are applied on the privileges of
function owner).

> We might need to consider that a portal has a local value of
> "current_user", which is kind of a pain, but probably doable.
>
It seems to me, it is an individual matter to be fixed independent
from RLS feature. How about your opinion?

If we go ahead, an idea to tackle this matter is switch user-id
into saved one in the Portal at the beginning of PortanRun or
PortalRunFetch.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-07-15 09:52                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-07-15 09:52 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Florian Pflug <fgp@phlo.org>; Robert Haas <robertmhaas@gmail.com>; pgsql-hackers

The attached patch is a revised version of row-level security feature.

I added a feature to invalidate plan cache if user-id was switched
between planner and optimizer. It enabled to generate more optimized
plan than the previous approach; that adds hardwired "OR superuser()".

Example)
postgres=# PREPARE p1(int) AS SELECT * FROM t1 WHERE x > $1 AND f_leak(y);
PREPARE
postgres=# EXPLAIN (costs off) EXECUTE p1(2);
            QUERY PLAN
-----------------------------------
 Seq Scan on t1
   Filter: (f_leak(y) AND (x > 2))
(2 rows)

postgres=# SET SESSION AUTHORIZATION alice;
SET
postgres=> EXPLAIN (costs off) EXECUTE p1(2);
                 QUERY PLAN
---------------------------------------------
 Subquery Scan on t1
   Filter: f_leak(t1.y)
   ->  Seq Scan on t1
         Filter: ((x > 2) AND ((x % 2) = 0))
(4 rows)

On the other hand, I removed support for UPDATE / DELETE commands
in this revision, because I'm still uncertain on the implementation that I
adopted in the previous patch. I believe it helps to keep patch size being
minimum reasonable.
Due to same reason, RLS is not supported on COPY TO command.

According to the Robert's comment, I revised the place to inject
applyRowLevelSecurity(). The reason why it needed to patch on
adjust_appendrel_attrs_mutator() was, we handled expansion from
regular relation to sub-query after expand_inherited_tables().
In this revision, it was moved to the head of sub-query planner.

Even though I added a syscache entry for pg_rowlevelsec catalog,
it was revised to read the catalog on construction of relcache, like
trigger descriptor, because it enables to reduce cost to parse an
expression tree in text format and memory consumption of hash
slot.

This revision adds support on pg_dump, and also adds support
to include SubLinks in the row-level security policy.

Thanks,

2012/7/1 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> 2012/6/28 Tom Lane <tgl@sss.pgh.pa.us>:
>> Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
>>> 2012/6/27 Florian Pflug <fgp@phlo.org>:
>>>> Hm, what happens if a SECURITY DEFINER functions returns a refcursor?
>>
>>> My impression is, here is no matter even if SECURITY DEFINER function
>>> returns refcursor.
>>
>> I think Florian has a point: it *should* work, but *will* it?
>>
>> I believe it works today, because the executor only applies permissions
>> checks during query startup.  So those checks are executed while still
>> within the SECURITY DEFINER context, and should behave as expected.
>> Subsequently, the cursor portal is returned to caller and caller can
>> execute it to completion, no problem.
>>
>> However, with RLS security-related checks will happen throughout the
>> execution of the portal.  They might do the wrong thing once the
>> SECURITY DEFINER function has been exited.
>>
> I tried the scenario that Florian pointed out.
>
> postgres=# CREATE OR REPLACE FUNCTION f_test(refcursor) RETURNS refcursor
> postgres-# SECURITY DEFINER LANGUAGE plpgsql
> postgres-# AS 'BEGIN OPEN $1 FOR SELECT current_user, * FROM t1;
> RETURN $1; END';
> CREATE FUNCTION
> postgres=# BEGIN;
> BEGIN
> postgres=# SELECT f_test('abc');
>  f_test
> --------
>  abc
> (1 row)
>
> postgres=# FETCH abc;
>  current_user | a |  b
> --------------+---+-----
>  kaigai       | 1 | aaa
> (1 row)
>
> postgres=# SET SESSION AUTHORIZATION alice;
> SET
> postgres=> FETCH abc;
>  current_user | a |  b
> --------------+---+-----
>  alice        | 2 | bbb
> (1 row)
>
> postgres=> SET SESSION AUTHORIZATION bob;
> SET
> postgres=> FETCH abc;
>  current_user | a |  b
> --------------+---+-----
>  bob          | 3 | ccc
> (1 row)
>
> Indeed, the output of "current_user" depends on the setting of session
> authorization, even though it was declared within security definer
> functions (thus, its security checks are applied on the privileges of
> function owner).
>
>> We might need to consider that a portal has a local value of
>> "current_user", which is kind of a pain, but probably doable.
>>
> It seems to me, it is an individual matter to be fixed independent
> from RLS feature. How about your opinion?
>
> If we go ahead, an idea to tackle this matter is switch user-id
> into saved one in the Portal at the beginning of PortanRun or
> PortalRunFetch.
>
> Thanks,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.ro.v2.patch (115.8K, ../../CADyhKSUXVffLJrmfMpi2xH=U+bFxu9Qa4w7kVhQoxkMfQOJ3yw@mail.gmail.com/2-pgsql-v9.3-row-level-security.ro.v2.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |   59 +++
 doc/src/sgml/ref/alter_table.sgml          |   38 ++
 doc/src/sgml/user-manag.sgml               |  123 +++++
 src/backend/access/transam/xact.c          |   12 +
 src/backend/catalog/Makefile               |    4 +-
 src/backend/catalog/dependency.c           |   21 +
 src/backend/catalog/heap.c                 |    1 +
 src/backend/catalog/pg_rowlevelsec.c       |  250 +++++++++
 src/backend/commands/tablecmds.c           |   64 +++
 src/backend/optimizer/plan/planner.c       |   17 +
 src/backend/optimizer/util/Makefile        |    2 +-
 src/backend/optimizer/util/rowlevelsec.c   |  677 +++++++++++++++++++++++++
 src/backend/parser/gram.y                  |   16 +
 src/backend/utils/adt/ri_triggers.c        |   14 +
 src/backend/utils/cache/plancache.c        |   32 ++
 src/backend/utils/cache/relcache.c         |   17 +-
 src/bin/pg_dump/pg_dump.c                  |   76 ++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/include/catalog/dependency.h           |    1 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |   20 +-
 src/include/catalog/pg_rowlevelsec.h       |   60 +++
 src/include/nodes/parsenodes.h             |    5 +-
 src/include/nodes/plannodes.h              |    2 +
 src/include/nodes/relation.h               |    2 +
 src/include/optimizer/rowlevelsec.h        |   31 ++
 src/include/utils/plancache.h              |    2 +
 src/include/utils/rel.h                    |    2 +
 src/test/regress/expected/rowlevelsec.out  |  753 ++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |    3 +-
 src/test/regress/parallel_schedule         |    2 +-
 src/test/regress/serial_schedule           |    1 +
 src/test/regress/sql/rowlevelsec.sql       |  237 +++++++++
 33 files changed, 2522 insertions(+), 26 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index fbd6d0d..426274c 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -229,6 +229,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1802,6 +1807,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4819,6 +4834,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 3f61d7d..6cae6c3 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -806,6 +831,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 49def6a..1b88a96 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -142,6 +143,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -171,6 +173,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1762,6 +1765,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2293,6 +2298,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4186,6 +4194,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4325,6 +4336,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 62fc9b0..de840b2 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index 98ce598..d18584b 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1221,6 +1222,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2269,6 +2274,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case ExtensionRelationId:
 			return OCLASS_EXTENSION;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2903,6 +2911,19 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c91df90..0d806de 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -775,6 +775,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..bd89c48
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,250 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								"ROW LEVEL SECURITY");
+	/* No aggregate function support */
+	if (pstate->p_hasAggs)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("cannot use aggregate in row-level security")));
+	/* No window function support */
+	if (pstate->p_hasWindowFuncs)
+		ereport(ERROR,
+				(errcode(ERRCODE_WINDOWING_ERROR),
+				 errmsg("cannot use window function in row-level security")));
+
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Indert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+void
+ResetRowLevelSecurity(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d69809a..6aae6f8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3108,6 +3112,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7558,6 +7570,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9656,6 +9684,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+		class_form->relhasrowlevelsec = true;
+	else
+		class_form->relhasrowlevelsec = false;
+
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index df76341..dabcfc6 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -32,6 +32,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -163,6 +164,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 */
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -240,6 +248,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = list_length(glob->paramlist);
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -314,6 +323,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		SS_process_ctes(root);
 
 	/*
+	 * Apply row-level security policy of appeared tables, if configured.
+	 * It must be applied prior to preprocess_rowmarks().
+	 *
+	 *
+	 */
+	applyRowLevelSecurity(root);
+
+	/*
 	 * Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
 	 * to transform them into joins.  Note that this step does not descend
 	 * into subqueries; if we pull up any subqueries below, their SubLinks are
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..70a1336
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,677 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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/sysattr.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * pull_rowlevel_security_policy
+ *
+ * This routine tries to pull expression node of row-level security policy
+ * on the target relation, and its children if configured.
+ * If one or more relation has a security policy at least, this function
+ * returns true, or false elsewhere.
+ */
+static bool
+pull_rowlevel_security_policy(PlannerInfo *root,
+							  RangeTblEntry *rte,
+							  Index rtindex,
+							  List **rls_relids,
+							  List **rls_quals,
+							  List **rls_flags)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *qual_list = NIL;
+	List		   *flag_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == root->parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, root->parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr   *qual = NULL;
+		int		flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = copyObject(rlsdesc->rlsqual);
+			if (rlsdesc->rlshassublinks)
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+		}
+
+		/*
+		 * Also, ask extensions whether they want to apply their own
+		 * row-level security policy. If both built-in and extension
+		 * has their own policy, it shall be merged.
+		 */
+		if (rowlevel_security_hook)
+		{
+			List   *qual_list;
+
+			qual_list = (*rowlevel_security_hook)(root, rel);
+			if (qual_list != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *)qual_list))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					qual_list = lappend(qual_list, qual);
+
+				if (list_length(qual_list) == 1)
+					qual = (Expr *)list_head(qual_list);
+				else
+					qual = makeBoolExpr(AND_EXPR, qual_list, -1);
+			}
+		}
+
+		qual_list = lappend(qual_list, qual);
+		if (qual)
+			result = true;
+		flag_list = lappend_int(flag_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	/*
+	 * Inform the caller list of relation Oid, qualifier of row-level
+	 * security policy and its flag, if one or more target relations
+	 * have its row-level security policy. Elsewhere, release it.
+	 */
+	if (result)
+	{
+		*rls_relids = relid_list;
+		*rls_quals = qual_list;
+		*rls_flags = flag_list;
+	}
+	else
+	{
+		list_free(relid_list);
+		list_free(qual_list);
+		list_free(flag_list);
+	}
+	return result;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	PlannerInfo *root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List       *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry    *subtle = lfirst(cell);
+
+				if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+					 var->varattno == InvalidAttrNumber) ||
+					(IsA(subtle->expr, Var) &&
+					 var->varattno == ((Var *)(subtle->expr))->varattno))
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context   context;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy.
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+}
+
+/*
+ * make_pseudo_column
+ *
+ * make a TargetEntry object which references a particular column of
+ * the underlying table.
+ */
+static TargetEntry *
+make_pseudo_column(Oid relid_head, Oid relid, AttrNumber attnum)
+{
+	Form_pg_attribute attform;
+	HeapTuple	tuple;
+	Var		   *var;
+	char	   *resname;
+
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(relid_head);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		return makeTargetEntry((Expr *) r, -1, get_rel_name(relid), false);
+	}
+
+	tuple = SearchSysCache2(ATTNUM,
+							ObjectIdGetDatum(relid),
+							Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+	var = makeVar((Index) 1,
+				  attform->attnum,
+				  attform->atttypid,
+				  attform->atttypmod,
+				  InvalidOid,
+				  0);
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(tuple);
+
+	return makeTargetEntry((Expr *)var, -1, resname, false);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(Oid relid_head,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 List *targetList,
+					 RangeTblEntry *rte,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	List		   *colnameList = NIL;
+	ListCell	   *cell;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = rte->requiredPerms;
+	subrte->selectedCols = NULL;
+	subrte->modifiedCols = NULL;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry	   *oldtle = lfirst(cell);
+		TargetEntry	   *subtle;
+		AttrNumber		attnum;
+
+		if (IsA(oldtle->expr, ConvertRowtypeExpr))
+			attnum = InvalidAttrNumber;
+		else
+		{
+			Assert(IsA(oldtle->expr, Var));
+
+			attnum = get_attnum(relid, oldtle->resname);
+			if (attnum == InvalidAttrNumber)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+								oldtle->resname, get_rel_name(relid))));
+		}
+		subtle = make_pseudo_column(relid_head, relid, attnum);
+		subtle->resno = oldtle->resno;
+		subqry->targetList = lappend(subqry->targetList, subtle);
+
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		subrte->selectedCols = bms_add_member(rte->selectedCols, attnum);
+	}
+	subrte->eref = makeAlias(get_rel_name(relid), colnameList);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * This routine expand reference to the given RangeTblEntry by a sub-query
+ * which simply references the target relation with the qualifier of row-
+ * level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Index rtindex,
+						   List *rls_relids,
+						   List *rls_quals,
+						   List *rls_flags)
+{
+	Query		   *parse = root->parse;
+	Oid				relid_head;
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	List		   *targetList;
+	List		   *colnameList;
+	Bitmapset	   *attr_used;
+	AttrNumber		attnum;
+	RowMarkClause  *rclause;
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+	ListCell	   *cell3;
+	ListCell	   *lc;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 0);
+
+	/*
+	 * Construct a target-entry list
+	 */
+	targetList = NIL;
+	colnameList = NIL;
+	relid_head = linitial_oid(rls_relids);
+	attr_used = bms_union(rte->selectedCols,
+						  rte->modifiedCols);
+	while ((attnum = bms_first_member(attr_used)) >= 0)
+	{
+		attnum += FirstLowInvalidHeapAttributeNumber;
+
+		subtle = make_pseudo_column(relid_head, relid_head, attnum);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	bms_free(attr_used);
+
+	/*
+	 * Push-down row-level lock of the target relation, since sub-query
+	 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+	 */
+	rclause = NULL;
+	foreach (lc, parse->rowMarks)
+	{
+		if (((RowMarkClause *) lfirst(lc))->rti == rtindex)
+		{
+			rclause = lfirst(lc);
+			parse->rowMarks = list_delete(parse->rowMarks, rclause);
+			break;
+		}
+	}
+
+	/*
+	 * Construct sub-query structures
+	 */
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid			relid = lfirst_oid(cell1);
+		Node	   *qual = lfirst(cell2);
+		int			flags = lfirst_int(cell3);
+
+		subqry = make_pseudo_subquery(relid_head, relid, qual, flags,
+									  targetList, rte, rclause);
+		if (cell1 == list_head(rls_relids))
+		{
+			Assert(rte->relid == relid);
+			Assert(rte->inh == true || list_length(rls_relids) == 1);
+
+			rte->relid = InvalidOid;
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = subqry;
+			rte->security_barrier = true;
+
+			/* no permission checks are needed to subquery itself */
+			rte->requiredPerms = 0;
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->modifiedCols = NULL;
+
+			rte->alias = NULL;
+			rte->eref = makeAlias(get_rel_name(relid),
+								  copyObject(colnameList));
+			if (list_length(rls_relids) == 1)
+				rte->inh = false;
+		}
+
+		/*
+		 * RTE's for child relations and AppendRelInfo in case when
+		 * the target relation has its children.
+		 */
+		if (list_length(rls_relids) > 1)
+		{
+			AppendRelInfo  *apinfo;
+			AttrNumber		child_rtindex;
+			ListCell	   *l;
+
+			/*
+			 * XXX - Set up RangeTblEntry for the child relation.
+			 * Note that it does not need to have security_barrier
+			 * attribute, if no row-level security policy is
+			 * configured on.
+			 */
+			subrte = makeNode(RangeTblEntry);
+			subrte->rtekind = RTE_SUBQUERY;
+			subrte->subquery = subqry;
+			if (qual)
+				subrte->security_barrier = true;
+			subrte->alias = NULL;
+			subrte->eref = makeAlias(get_rel_name(relid),
+									 copyObject(colnameList));
+			parse->rtable = lappend(parse->rtable, subrte);
+			child_rtindex = list_length(parse->rtable);
+
+			/*
+			 * reference to inherited children performs as if simple
+			 * UNION ALL operation, so add AppendRelInfo here.
+			 */
+			apinfo = makeNode(AppendRelInfo);
+			apinfo->parent_relid = rtindex;
+			apinfo->child_relid = child_rtindex;
+			foreach (l, targetList)
+			{
+				Var	   *trans_var
+					= makeVarFromTargetEntry(child_rtindex, lfirst(l));
+				apinfo->translated_vars
+					= lappend(apinfo->translated_vars, trans_var);
+			}
+			root->append_rel_list = lappend(root->append_rel_list, apinfo);
+		}
+	}
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+applyRowLevelSecurity(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		has_rowlevel_sec = false;
+
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		List		   *rls_relids;
+		List		   *rls_quals;
+		List		   *rls_flags;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * XXX - In this revision, we have no support for UPDATE / DELETE
+		 * statement, so simply skip it.
+		 */
+		if (rtindex == parse->resultRelation)
+			continue;
+
+		/*
+		 * In case when a row-level security policy was configured on
+		 * the table referenced by this RangeTblEntry or its children,
+		 * it shall be rewritten to sub-query with this policy.
+		 */
+		if (pull_rowlevel_security_policy(root, rte, rtindex,
+										  &rls_relids, &rls_quals, &rls_flags))
+		{
+			expand_rtentry_with_policy(root, rte, rtindex,
+									   rls_relids, rls_quals, rls_flags);
+			has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * Post case handling if one or more relation was replaced to sub-query.
+	 */
+	if (has_rowlevel_sec)
+	{
+		PlanInvalItem  *pi_item;
+
+		/*
+		 * plan should be invalidated if and when userid was changed
+		 * on the executor stage, from planner stage.
+		 */
+		Assert(!OidIsValid(root->glob->planUserId) ||
+			   root->glob->planUserId == GetUserId());
+		root->glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue
+			= GetSysCacheHashValue1(AUTHOID,
+									ObjectIdGetDatum(root->glob->planUserId));
+		root->glob->invalItems = lappend(root->glob->invalItems, pi_item);
+
+		/*
+		 * Since the relation with RLS policy was replaced by a sub-query,
+		 * thus resource number to reference a particular column can be
+		 * also moditifed. If we applied RLS policy on one or more relations,
+		 * varattno of Var node that has referenced the rewritten relation
+		 * needs to be fixed up.
+		 */
+		fixup_varattnos(root);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e6ceed..8c0a8a4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2058,6 +2058,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 983f631..c53cbfd 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -42,6 +42,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2998,6 +2999,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3080,6 +3082,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3089,6 +3100,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c42765c..5ebd944 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -664,6 +665,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -715,6 +726,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -793,6 +806,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -827,6 +858,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2e6776e..11051ed 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -895,6 +896,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1784,6 +1790,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3023,7 +3031,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4173,6 +4187,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7d67287..0291590 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3865,6 +3865,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3889,7 +3890,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3909,7 +3948,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3945,7 +3985,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3980,7 +4021,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4015,7 +4057,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4051,7 +4094,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4086,7 +4130,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4117,7 +4162,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4143,7 +4189,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4179,7 +4226,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4227,6 +4275,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4269,6 +4318,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12707,6 +12760,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index b44187bb..20803ab 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -255,6 +255,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index f0eb564..fef31b7 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -146,6 +146,7 @@ typedef enum ObjectClass
 	OCLASS_USER_MAPPING,		/* pg_user_mapping */
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 450ec25..5e4467d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -306,6 +306,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 50111cb..de85fff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1227,6 +1228,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index cf0bbd9..26f986a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..9afe00a
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(PlannerInfo *root,
+											 Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..5673e7a
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,753 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (rls_regress_schema.document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (rls_regress_schema.document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (d float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(c);
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  c  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  c  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  Seq Scan on t3
+(10 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+                           Filter: f_leak(c)
+(16 rows)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+           QUERY PLAN            
+---------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t2 t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t3 t1
+               Filter: f_leak(c)
+(8 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+(6 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+           QUERY PLAN           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a <= 2)
+(8 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+          QUERY PLAN           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a = 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 7 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 7f560d2..fd50d9e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -119,6 +119,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -164,7 +165,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(153 rows)
+(154 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8852e0a..d368504 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0bc5df7..290b24b 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a4d1ccd
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,237 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- 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 (d 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 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+301	1	xxx	X
+302	2	yyy	Y
+303	3	zzz	Z
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- reference to system column
+SELECT oid, * FROM t1;
+EXPLAIN (costs off) SELECT * 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(c) FOR SHARE;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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;
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-07-17 03:49                         ` Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2012-07-17 03:49 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> The attached patch is a revised version of row-level security feature.
>
> I added a feature to invalidate plan cache if user-id was switched
> between planner and optimizer. It enabled to generate more optimized
> plan than the previous approach; that adds hardwired "OR superuser()".
>
> Example)
> postgres=# PREPARE p1(int) AS SELECT * FROM t1 WHERE x > $1 AND f_leak(y);
> PREPARE
> postgres=# EXPLAIN (costs off) EXECUTE p1(2);
>             QUERY PLAN
> -----------------------------------
>  Seq Scan on t1
>    Filter: (f_leak(y) AND (x > 2))
> (2 rows)
>
> postgres=# SET SESSION AUTHORIZATION alice;
> SET
> postgres=> EXPLAIN (costs off) EXECUTE p1(2);
>                  QUERY PLAN
> ---------------------------------------------
>  Subquery Scan on t1
>    Filter: f_leak(t1.y)
>    ->  Seq Scan on t1
>          Filter: ((x > 2) AND ((x % 2) = 0))
> (4 rows)
>
> On the other hand, I removed support for UPDATE / DELETE commands
> in this revision, because I'm still uncertain on the implementation that I
> adopted in the previous patch. I believe it helps to keep patch size being
> minimum reasonable.
> Due to same reason, RLS is not supported on COPY TO command.
>
> According to the Robert's comment, I revised the place to inject
> applyRowLevelSecurity(). The reason why it needed to patch on
> adjust_appendrel_attrs_mutator() was, we handled expansion from
> regular relation to sub-query after expand_inherited_tables().
> In this revision, it was moved to the head of sub-query planner.
>
> Even though I added a syscache entry for pg_rowlevelsec catalog,
> it was revised to read the catalog on construction of relcache, like
> trigger descriptor, because it enables to reduce cost to parse an
> expression tree in text format and memory consumption of hash
> slot.
>
> This revision adds support on pg_dump, and also adds support
> to include SubLinks in the row-level security policy.

This revision is too late for this CommitFest; I've moved it to the
next CommitFest and will look at it then, or hopefully sooner.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2012-07-17 04:02                           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-07-17 04:02 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/7/17 Robert Haas <robertmhaas@gmail.com>:
> On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> The attached patch is a revised version of row-level security feature.
>>
>> I added a feature to invalidate plan cache if user-id was switched
>> between planner and optimizer. It enabled to generate more optimized
>> plan than the previous approach; that adds hardwired "OR superuser()".
>>
>> Example)
>> postgres=# PREPARE p1(int) AS SELECT * FROM t1 WHERE x > $1 AND f_leak(y);
>> PREPARE
>> postgres=# EXPLAIN (costs off) EXECUTE p1(2);
>>             QUERY PLAN
>> -----------------------------------
>>  Seq Scan on t1
>>    Filter: (f_leak(y) AND (x > 2))
>> (2 rows)
>>
>> postgres=# SET SESSION AUTHORIZATION alice;
>> SET
>> postgres=> EXPLAIN (costs off) EXECUTE p1(2);
>>                  QUERY PLAN
>> ---------------------------------------------
>>  Subquery Scan on t1
>>    Filter: f_leak(t1.y)
>>    ->  Seq Scan on t1
>>          Filter: ((x > 2) AND ((x % 2) = 0))
>> (4 rows)
>>
>> On the other hand, I removed support for UPDATE / DELETE commands
>> in this revision, because I'm still uncertain on the implementation that I
>> adopted in the previous patch. I believe it helps to keep patch size being
>> minimum reasonable.
>> Due to same reason, RLS is not supported on COPY TO command.
>>
>> According to the Robert's comment, I revised the place to inject
>> applyRowLevelSecurity(). The reason why it needed to patch on
>> adjust_appendrel_attrs_mutator() was, we handled expansion from
>> regular relation to sub-query after expand_inherited_tables().
>> In this revision, it was moved to the head of sub-query planner.
>>
>> Even though I added a syscache entry for pg_rowlevelsec catalog,
>> it was revised to read the catalog on construction of relcache, like
>> trigger descriptor, because it enables to reduce cost to parse an
>> expression tree in text format and memory consumption of hash
>> slot.
>>
>> This revision adds support on pg_dump, and also adds support
>> to include SubLinks in the row-level security policy.
>
> This revision is too late for this CommitFest; I've moved it to the
> next CommitFest and will look at it then, or hopefully sooner.
>
It seems to me fair enough. I may be able to add UPDATE /
DELETE support until next commit-fest.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-09-02 21:39                             ` Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Dean Rasheed @ 2012-09-02 21:39 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On 17 July 2012 05:02, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> 2012/7/17 Robert Haas <robertmhaas@gmail.com>:
>> On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>> The attached patch is a revised version of row-level security feature.
>>> ...
>>> According to the Robert's comment, I revised the place to inject
>>> applyRowLevelSecurity(). The reason why it needed to patch on
>>> adjust_appendrel_attrs_mutator() was, we handled expansion from
>>> regular relation to sub-query after expand_inherited_tables().
>>> In this revision, it was moved to the head of sub-query planner.
>>>

Hi,

I had a quick look at this and spotted a problem - certain types of
query are able to bypass the RLS quals. For example:

SELECT * FROM (SELECT * FROM foo) foo;

since the RLS policy doesn't descend into subqueries, and is applied
before they are pulled up into the main query. Similarly for views on
top of tables with RLS, and SRF functions that query a table with RLS
that get inlined.

Also queries using UNION ALL are vulnerable if they end up being
flattened, for example:

SELECT * FROM foo UNION ALL SELECT * FROM foo;


FWIW I recently developed some similar code as part of a patch to
implement automatically updatable views
(http://archives.postgresql.org/pgsql-hackers/2012-08/msg00303.php).
Some parts of that code may be useful, possibly for adding
UPDATE/DELETE support.

Regards,
Dean




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2012-09-03 20:32                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-09-03 20:32 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/9/2 Dean Rasheed <dean.a.rasheed@gmail.com>:
> On 17 July 2012 05:02, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> 2012/7/17 Robert Haas <robertmhaas@gmail.com>:
>>> On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>>> The attached patch is a revised version of row-level security feature.
>>>> ...
>>>> According to the Robert's comment, I revised the place to inject
>>>> applyRowLevelSecurity(). The reason why it needed to patch on
>>>> adjust_appendrel_attrs_mutator() was, we handled expansion from
>>>> regular relation to sub-query after expand_inherited_tables().
>>>> In this revision, it was moved to the head of sub-query planner.
>>>>
>
> Hi,
>
> I had a quick look at this and spotted a problem - certain types of
> query are able to bypass the RLS quals. For example:
>
> SELECT * FROM (SELECT * FROM foo) foo;
>
> since the RLS policy doesn't descend into subqueries, and is applied
> before they are pulled up into the main query. Similarly for views on
> top of tables with RLS, and SRF functions that query a table with RLS
> that get inlined.
>
> Also queries using UNION ALL are vulnerable if they end up being
> flattened, for example:
>
> SELECT * FROM foo UNION ALL SELECT * FROM foo;
>
Thanks for your comment.

Indeed, I missed the case of simple sub-queries and union-all being
pulled up into the main query. So, I adjusted the location to invoke
applyRowLevelSecurity() between all the pull-up stuff and expanding
inherited tables.

The attached patch is a fixed and rebased revision for CF:Sep.

> FWIW I recently developed some similar code as part of a patch to
> implement automatically updatable views
> (http://archives.postgresql.org/pgsql-hackers/2012-08/msg00303.php).
> Some parts of that code may be useful, possibly for adding
> UPDATE/DELETE support.
>
Let me check it.

Best regards,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.ro.v3.patch (117.9K, ../../CADyhKSWcd6i5c30-AzSZ1RSge4s7vw+nahi36Vf2g_UkwuA4sA@mail.gmail.com/2-pgsql-v9.3-row-level-security.ro.v3.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  59 +++
 doc/src/sgml/ref/alter_table.sgml          |  38 ++
 doc/src/sgml/user-manag.sgml               | 123 +++++
 src/backend/access/transam/xact.c          |  12 +
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 240 +++++++++
 src/backend/commands/tablecmds.c           |  64 +++
 src/backend/optimizer/plan/planner.c       |  17 +
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 677 ++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/utils/adt/ri_triggers.c        |  14 +
 src/backend/utils/cache/plancache.c        |  32 ++
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/pg_dump.c                  |  76 ++-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |  20 +-
 src/include/catalog/pg_rowlevelsec.h       |  60 +++
 src/include/nodes/parsenodes.h             |   5 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  31 ++
 src/include/parser/parse_node.h            |   3 +-
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 753 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowlevelsec.sql       | 237 +++++++++
 36 files changed, 2525 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bcd82c..5a5233e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index b764814..f3f2d91 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -806,6 +831,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e7a6606..851f9db 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -144,6 +145,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -173,6 +175,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1764,6 +1767,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2295,6 +2300,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4188,6 +4196,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4327,6 +4338,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index 87d6f02..f0736b1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1229,6 +1230,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2280,6 +2285,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2929,6 +2937,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c80df41..60de76d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..4241b95
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,240 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Indert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+void
+ResetRowLevelSecurity(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 359d478..83ca86b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3108,6 +3112,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7533,6 +7545,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9631,6 +9659,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+		class_form->relhasrowlevelsec = true;
+	else
+		class_form->relhasrowlevelsec = false;
+
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4284eed..19758c0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 */
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +252,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = list_length(glob->paramlist);
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -318,6 +327,14 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		SS_process_ctes(root);
 
 	/*
+	 * Apply row-level security policy of appeared tables, if configured.
+	 * It must be applied prior to preprocess_rowmarks().
+	 *
+	 *
+	 */
+	applyRowLevelSecurity(root);
+
+	/*
 	 * Look for ANY and EXISTS SubLinks in WHERE and JOIN/ON clauses, and try
 	 * to transform them into joins.  Note that this step does not descend
 	 * into subqueries; if we pull up any subqueries below, their SubLinks are
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..70a1336
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,677 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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/sysattr.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_inherits_fn.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * pull_rowlevel_security_policy
+ *
+ * This routine tries to pull expression node of row-level security policy
+ * on the target relation, and its children if configured.
+ * If one or more relation has a security policy at least, this function
+ * returns true, or false elsewhere.
+ */
+static bool
+pull_rowlevel_security_policy(PlannerInfo *root,
+							  RangeTblEntry *rte,
+							  Index rtindex,
+							  List **rls_relids,
+							  List **rls_quals,
+							  List **rls_flags)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *qual_list = NIL;
+	List		   *flag_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == root->parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, root->parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr   *qual = NULL;
+		int		flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = copyObject(rlsdesc->rlsqual);
+			if (rlsdesc->rlshassublinks)
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+		}
+
+		/*
+		 * Also, ask extensions whether they want to apply their own
+		 * row-level security policy. If both built-in and extension
+		 * has their own policy, it shall be merged.
+		 */
+		if (rowlevel_security_hook)
+		{
+			List   *qual_list;
+
+			qual_list = (*rowlevel_security_hook)(root, rel);
+			if (qual_list != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *)qual_list))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					qual_list = lappend(qual_list, qual);
+
+				if (list_length(qual_list) == 1)
+					qual = (Expr *)list_head(qual_list);
+				else
+					qual = makeBoolExpr(AND_EXPR, qual_list, -1);
+			}
+		}
+
+		qual_list = lappend(qual_list, qual);
+		if (qual)
+			result = true;
+		flag_list = lappend_int(flag_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	/*
+	 * Inform the caller list of relation Oid, qualifier of row-level
+	 * security policy and its flag, if one or more target relations
+	 * have its row-level security policy. Elsewhere, release it.
+	 */
+	if (result)
+	{
+		*rls_relids = relid_list;
+		*rls_quals = qual_list;
+		*rls_flags = flag_list;
+	}
+	else
+	{
+		list_free(relid_list);
+		list_free(qual_list);
+		list_free(flag_list);
+	}
+	return result;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	PlannerInfo *root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List       *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry    *subtle = lfirst(cell);
+
+				if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+					 var->varattno == InvalidAttrNumber) ||
+					(IsA(subtle->expr, Var) &&
+					 var->varattno == ((Var *)(subtle->expr))->varattno))
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context   context;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy.
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+}
+
+/*
+ * make_pseudo_column
+ *
+ * make a TargetEntry object which references a particular column of
+ * the underlying table.
+ */
+static TargetEntry *
+make_pseudo_column(Oid relid_head, Oid relid, AttrNumber attnum)
+{
+	Form_pg_attribute attform;
+	HeapTuple	tuple;
+	Var		   *var;
+	char	   *resname;
+
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(relid_head);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		return makeTargetEntry((Expr *) r, -1, get_rel_name(relid), false);
+	}
+
+	tuple = SearchSysCache2(ATTNUM,
+							ObjectIdGetDatum(relid),
+							Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+	var = makeVar((Index) 1,
+				  attform->attnum,
+				  attform->atttypid,
+				  attform->atttypmod,
+				  InvalidOid,
+				  0);
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(tuple);
+
+	return makeTargetEntry((Expr *)var, -1, resname, false);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(Oid relid_head,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 List *targetList,
+					 RangeTblEntry *rte,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	List		   *colnameList = NIL;
+	ListCell	   *cell;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = rte->requiredPerms;
+	subrte->selectedCols = NULL;
+	subrte->modifiedCols = NULL;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry	   *oldtle = lfirst(cell);
+		TargetEntry	   *subtle;
+		AttrNumber		attnum;
+
+		if (IsA(oldtle->expr, ConvertRowtypeExpr))
+			attnum = InvalidAttrNumber;
+		else
+		{
+			Assert(IsA(oldtle->expr, Var));
+
+			attnum = get_attnum(relid, oldtle->resname);
+			if (attnum == InvalidAttrNumber)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+								oldtle->resname, get_rel_name(relid))));
+		}
+		subtle = make_pseudo_column(relid_head, relid, attnum);
+		subtle->resno = oldtle->resno;
+		subqry->targetList = lappend(subqry->targetList, subtle);
+
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		subrte->selectedCols = bms_add_member(rte->selectedCols, attnum);
+	}
+	subrte->eref = makeAlias(get_rel_name(relid), colnameList);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * This routine expand reference to the given RangeTblEntry by a sub-query
+ * which simply references the target relation with the qualifier of row-
+ * level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Index rtindex,
+						   List *rls_relids,
+						   List *rls_quals,
+						   List *rls_flags)
+{
+	Query		   *parse = root->parse;
+	Oid				relid_head;
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	List		   *targetList;
+	List		   *colnameList;
+	Bitmapset	   *attr_used;
+	AttrNumber		attnum;
+	RowMarkClause  *rclause;
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+	ListCell	   *cell3;
+	ListCell	   *lc;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 0);
+
+	/*
+	 * Construct a target-entry list
+	 */
+	targetList = NIL;
+	colnameList = NIL;
+	relid_head = linitial_oid(rls_relids);
+	attr_used = bms_union(rte->selectedCols,
+						  rte->modifiedCols);
+	while ((attnum = bms_first_member(attr_used)) >= 0)
+	{
+		attnum += FirstLowInvalidHeapAttributeNumber;
+
+		subtle = make_pseudo_column(relid_head, relid_head, attnum);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	bms_free(attr_used);
+
+	/*
+	 * Push-down row-level lock of the target relation, since sub-query
+	 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+	 */
+	rclause = NULL;
+	foreach (lc, parse->rowMarks)
+	{
+		if (((RowMarkClause *) lfirst(lc))->rti == rtindex)
+		{
+			rclause = lfirst(lc);
+			parse->rowMarks = list_delete(parse->rowMarks, rclause);
+			break;
+		}
+	}
+
+	/*
+	 * Construct sub-query structures
+	 */
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid			relid = lfirst_oid(cell1);
+		Node	   *qual = lfirst(cell2);
+		int			flags = lfirst_int(cell3);
+
+		subqry = make_pseudo_subquery(relid_head, relid, qual, flags,
+									  targetList, rte, rclause);
+		if (cell1 == list_head(rls_relids))
+		{
+			Assert(rte->relid == relid);
+			Assert(rte->inh == true || list_length(rls_relids) == 1);
+
+			rte->relid = InvalidOid;
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = subqry;
+			rte->security_barrier = true;
+
+			/* no permission checks are needed to subquery itself */
+			rte->requiredPerms = 0;
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->modifiedCols = NULL;
+
+			rte->alias = NULL;
+			rte->eref = makeAlias(get_rel_name(relid),
+								  copyObject(colnameList));
+			if (list_length(rls_relids) == 1)
+				rte->inh = false;
+		}
+
+		/*
+		 * RTE's for child relations and AppendRelInfo in case when
+		 * the target relation has its children.
+		 */
+		if (list_length(rls_relids) > 1)
+		{
+			AppendRelInfo  *apinfo;
+			AttrNumber		child_rtindex;
+			ListCell	   *l;
+
+			/*
+			 * XXX - Set up RangeTblEntry for the child relation.
+			 * Note that it does not need to have security_barrier
+			 * attribute, if no row-level security policy is
+			 * configured on.
+			 */
+			subrte = makeNode(RangeTblEntry);
+			subrte->rtekind = RTE_SUBQUERY;
+			subrte->subquery = subqry;
+			if (qual)
+				subrte->security_barrier = true;
+			subrte->alias = NULL;
+			subrte->eref = makeAlias(get_rel_name(relid),
+									 copyObject(colnameList));
+			parse->rtable = lappend(parse->rtable, subrte);
+			child_rtindex = list_length(parse->rtable);
+
+			/*
+			 * reference to inherited children performs as if simple
+			 * UNION ALL operation, so add AppendRelInfo here.
+			 */
+			apinfo = makeNode(AppendRelInfo);
+			apinfo->parent_relid = rtindex;
+			apinfo->child_relid = child_rtindex;
+			foreach (l, targetList)
+			{
+				Var	   *trans_var
+					= makeVarFromTargetEntry(child_rtindex, lfirst(l));
+				apinfo->translated_vars
+					= lappend(apinfo->translated_vars, trans_var);
+			}
+			root->append_rel_list = lappend(root->append_rel_list, apinfo);
+		}
+	}
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+applyRowLevelSecurity(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		has_rowlevel_sec = false;
+
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		List		   *rls_relids;
+		List		   *rls_quals;
+		List		   *rls_flags;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * XXX - In this revision, we have no support for UPDATE / DELETE
+		 * statement, so simply skip it.
+		 */
+		if (rtindex == parse->resultRelation)
+			continue;
+
+		/*
+		 * In case when a row-level security policy was configured on
+		 * the table referenced by this RangeTblEntry or its children,
+		 * it shall be rewritten to sub-query with this policy.
+		 */
+		if (pull_rowlevel_security_policy(root, rte, rtindex,
+										  &rls_relids, &rls_quals, &rls_flags))
+		{
+			expand_rtentry_with_policy(root, rte, rtindex,
+									   rls_relids, rls_quals, rls_flags);
+			has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * Post case handling if one or more relation was replaced to sub-query.
+	 */
+	if (has_rowlevel_sec)
+	{
+		PlanInvalItem  *pi_item;
+
+		/*
+		 * plan should be invalidated if and when userid was changed
+		 * on the executor stage, from planner stage.
+		 */
+		Assert(!OidIsValid(root->glob->planUserId) ||
+			   root->glob->planUserId == GetUserId());
+		root->glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue
+			= GetSysCacheHashValue1(AUTHOID,
+									ObjectIdGetDatum(root->glob->planUserId));
+		root->glob->invalItems = lappend(root->glob->invalItems, pi_item);
+
+		/*
+		 * Since the relation with RLS policy was replaced by a sub-query,
+		 * thus resource number to reference a particular column can be
+		 * also moditifed. If we applied RLS policy on one or more relations,
+		 * varattno of Var node that has referenced the rewritten relation
+		 * needs to be fixed up.
+		 */
+		fixup_varattnos(root);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5894cb0..0a86ce8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2068,6 +2068,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index d1d835b..87423d8 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..19a2255 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -43,6 +43,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2999,6 +3000,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3081,6 +3083,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3090,6 +3101,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0cdd30d..f37586e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4174,6 +4188,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index cdbed20..e781dae 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3867,6 +3867,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3891,7 +3892,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3911,7 +3950,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3947,7 +3987,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3982,7 +4023,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4017,7 +4059,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4053,7 +4096,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4088,7 +4132,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4119,7 +4164,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4145,7 +4191,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4181,7 +4228,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4229,6 +4277,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4271,6 +4320,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12803,6 +12856,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2aa2060..5485101 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -256,6 +256,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 19178b5..2f9de9f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1231,6 +1232,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index af9425a..73c6ff1 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..9afe00a
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(PlannerInfo *root,
+											 Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3bb35f..d13ef32 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..5673e7a
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,753 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (rls_regress_schema.document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (rls_regress_schema.document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (d float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(c);
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  c  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  c  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  Seq Scan on t3
+(10 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+                           Filter: f_leak(c)
+(16 rows)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+           QUERY PLAN            
+---------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t2 t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t3 t1
+               Filter: f_leak(c)
+(8 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+(6 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+           QUERY PLAN           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a <= 2)
+(8 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+          QUERY PLAN           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a = 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 7 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ac29194..f0541c0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8576a7f..e789395 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a4d1ccd
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,237 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- 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 (d 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 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+301	1	xxx	X
+302	2	yyy	Y
+303	3	zzz	Z
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- reference to system column
+SELECT oid, * FROM t1;
+EXPLAIN (costs off) SELECT * 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(c) FOR SHARE;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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;
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-09-05 20:49                                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-09-05 20:49 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/9/3 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> 2012/9/2 Dean Rasheed <dean.a.rasheed@gmail.com>:
>> On 17 July 2012 05:02, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>> 2012/7/17 Robert Haas <robertmhaas@gmail.com>:
>>>> On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>>>> The attached patch is a revised version of row-level security feature.
>>>>> ...
>>>>> According to the Robert's comment, I revised the place to inject
>>>>> applyRowLevelSecurity(). The reason why it needed to patch on
>>>>> adjust_appendrel_attrs_mutator() was, we handled expansion from
>>>>> regular relation to sub-query after expand_inherited_tables().
>>>>> In this revision, it was moved to the head of sub-query planner.
>>>>>
>>
>> Hi,
>>
>> I had a quick look at this and spotted a problem - certain types of
>> query are able to bypass the RLS quals. For example:
>>
>> SELECT * FROM (SELECT * FROM foo) foo;
>>
>> since the RLS policy doesn't descend into subqueries, and is applied
>> before they are pulled up into the main query. Similarly for views on
>> top of tables with RLS, and SRF functions that query a table with RLS
>> that get inlined.
>>
>> Also queries using UNION ALL are vulnerable if they end up being
>> flattened, for example:
>>
>> SELECT * FROM foo UNION ALL SELECT * FROM foo;
>>
> Thanks for your comment.
>
> Indeed, I missed the case of simple sub-queries and union-all being
> pulled up into the main query. So, I adjusted the location to invoke
> applyRowLevelSecurity() between all the pull-up stuff and expanding
> inherited tables.
>
> The attached patch is a fixed and rebased revision for CF:Sep.
>
Sorry! I attached incorrect revision. The attached patch is right one.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.ro.v3.patch (118.0K, ../../CADyhKSUEGdMeWKtAP6oFzn6e8QK9zFmhdqdpB_GF7O=sdADvAQ@mail.gmail.com/2-pgsql-v9.3-row-level-security.ro.v3.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  59 +++
 doc/src/sgml/ref/alter_table.sgml          |  38 ++
 doc/src/sgml/user-manag.sgml               | 123 +++++
 src/backend/access/transam/xact.c          |  12 +
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 241 +++++++++
 src/backend/commands/tablecmds.c           |  64 +++
 src/backend/optimizer/plan/planner.c       |  16 +
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 678 ++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/utils/adt/ri_triggers.c        |  14 +
 src/backend/utils/cache/plancache.c        |  32 ++
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/pg_dump.c                  |  76 ++-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |  20 +-
 src/include/catalog/pg_rowlevelsec.h       |  60 +++
 src/include/nodes/parsenodes.h             |   5 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  31 ++
 src/include/parser/parse_node.h            |   3 +-
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 753 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowlevelsec.sql       | 237 +++++++++
 36 files changed, 2526 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3bcd82c..5a5233e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index b764814..f3f2d91 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -806,6 +831,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e7a6606..851f9db 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -144,6 +145,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -173,6 +175,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1764,6 +1767,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2295,6 +2300,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4188,6 +4196,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4327,6 +4338,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index 87d6f02..f0736b1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1229,6 +1230,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2280,6 +2285,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2929,6 +2937,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c80df41..60de76d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..e554f20
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,241 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Indert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+void
+ResetRowLevelSecurity(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 359d478..83ca86b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3108,6 +3112,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7533,6 +7545,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9631,6 +9659,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+		class_form->relhasrowlevelsec = true;
+	else
+		class_form->relhasrowlevelsec = false;
+
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4284eed..7e6349c 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 */
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +252,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = list_length(glob->paramlist);
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -350,6 +359,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		flatten_simple_union_all(root);
 
 	/*
+	 * Apply row-level security policy of appeared tables, if configured.
+	 * Note that it must be processed after all the simple subqueries or
+	 * union all queries being flatten, but prior to preprocess_rowmarks().
+	 */
+	applyRowLevelSecurity(root);
+
+	/*
 	 * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
 	 * avoid the expense of doing flatten_join_alias_vars().  Also check for
 	 * outer joins --- if none, we can skip reduce_outer_joins().  And check
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..3a01b0c
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,678 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * pull_rowlevel_security_policy
+ *
+ * This routine tries to pull expression node of row-level security policy
+ * on the target relation, and its children if configured.
+ * If one or more relation has a security policy at least, this function
+ * returns true, or false elsewhere.
+ */
+static bool
+pull_rowlevel_security_policy(PlannerInfo *root,
+							  RangeTblEntry *rte,
+							  Index rtindex,
+							  List **rls_relids,
+							  List **rls_quals,
+							  List **rls_flags)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *qual_list = NIL;
+	List		   *flag_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == root->parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, root->parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr   *qual = NULL;
+		int		flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = copyObject(rlsdesc->rlsqual);
+			if (rlsdesc->rlshassublinks)
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+		}
+
+		/*
+		 * Also, ask extensions whether they want to apply their own
+		 * row-level security policy. If both built-in and extension
+		 * has their own policy, it shall be merged.
+		 */
+		if (rowlevel_security_hook)
+		{
+			List   *qual_list;
+
+			qual_list = (*rowlevel_security_hook)(root, rel);
+			if (qual_list != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *)qual_list))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					qual_list = lappend(qual_list, qual);
+
+				if (list_length(qual_list) == 1)
+					qual = (Expr *)list_head(qual_list);
+				else
+					qual = makeBoolExpr(AND_EXPR, qual_list, -1);
+			}
+		}
+
+		qual_list = lappend(qual_list, qual);
+		if (qual)
+			result = true;
+		flag_list = lappend_int(flag_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	/*
+	 * Inform the caller list of relation Oid, qualifier of row-level
+	 * security policy and its flag, if one or more target relations
+	 * have its row-level security policy. Elsewhere, release it.
+	 */
+	if (result)
+	{
+		*rls_relids = relid_list;
+		*rls_quals = qual_list;
+		*rls_flags = flag_list;
+	}
+	else
+	{
+		list_free(relid_list);
+		list_free(qual_list);
+		list_free(flag_list);
+	}
+	return result;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	PlannerInfo *root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List       *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry    *subtle = lfirst(cell);
+
+				if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+					 var->varattno == InvalidAttrNumber) ||
+					(IsA(subtle->expr, Var) &&
+					 var->varattno == ((Var *)(subtle->expr))->varattno))
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context   context;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy.
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+}
+
+/*
+ * make_pseudo_column
+ *
+ * make a TargetEntry object which references a particular column of
+ * the underlying table.
+ */
+static TargetEntry *
+make_pseudo_column(Oid relid_head, Oid relid, AttrNumber attnum)
+{
+	Form_pg_attribute attform;
+	HeapTuple	tuple;
+	Var		   *var;
+	char	   *resname;
+
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(relid_head);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		return makeTargetEntry((Expr *) r, -1, get_rel_name(relid), false);
+	}
+
+	tuple = SearchSysCache2(ATTNUM,
+							ObjectIdGetDatum(relid),
+							Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+	var = makeVar((Index) 1,
+				  attform->attnum,
+				  attform->atttypid,
+				  attform->atttypmod,
+				  InvalidOid,
+				  0);
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(tuple);
+
+	return makeTargetEntry((Expr *)var, -1, resname, false);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(Oid relid_head,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 List *targetList,
+					 RangeTblEntry *rte,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	List		   *colnameList = NIL;
+	ListCell	   *cell;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = rte->requiredPerms;
+	subrte->selectedCols = NULL;
+	subrte->modifiedCols = NULL;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry	   *oldtle = lfirst(cell);
+		TargetEntry	   *subtle;
+		AttrNumber		attnum;
+
+		if (IsA(oldtle->expr, ConvertRowtypeExpr))
+			attnum = InvalidAttrNumber;
+		else
+		{
+			Assert(IsA(oldtle->expr, Var));
+
+			attnum = get_attnum(relid, oldtle->resname);
+			if (attnum == InvalidAttrNumber)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+								oldtle->resname, get_rel_name(relid))));
+		}
+		subtle = make_pseudo_column(relid_head, relid, attnum);
+		subtle->resno = oldtle->resno;
+		subqry->targetList = lappend(subqry->targetList, subtle);
+
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		subrte->selectedCols = bms_add_member(rte->selectedCols, attnum);
+	}
+	subrte->eref = makeAlias(get_rel_name(relid), colnameList);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * This routine expand reference to the given RangeTblEntry by a sub-query
+ * which simply references the target relation with the qualifier of row-
+ * level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Index rtindex,
+						   List *rls_relids,
+						   List *rls_quals,
+						   List *rls_flags)
+{
+	Query		   *parse = root->parse;
+	Oid				relid_head;
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	List		   *targetList;
+	List		   *colnameList;
+	Bitmapset	   *attr_used;
+	AttrNumber		attnum;
+	RowMarkClause  *rclause;
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+	ListCell	   *cell3;
+	ListCell	   *lc;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 0);
+
+	/*
+	 * Construct a target-entry list
+	 */
+	targetList = NIL;
+	colnameList = NIL;
+	relid_head = linitial_oid(rls_relids);
+	attr_used = bms_union(rte->selectedCols,
+						  rte->modifiedCols);
+	while ((attnum = bms_first_member(attr_used)) >= 0)
+	{
+		attnum += FirstLowInvalidHeapAttributeNumber;
+
+		subtle = make_pseudo_column(relid_head, relid_head, attnum);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	bms_free(attr_used);
+
+	/*
+	 * Push-down row-level lock of the target relation, since sub-query
+	 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+	 */
+	rclause = NULL;
+	foreach (lc, parse->rowMarks)
+	{
+		if (((RowMarkClause *) lfirst(lc))->rti == rtindex)
+		{
+			rclause = lfirst(lc);
+			parse->rowMarks = list_delete(parse->rowMarks, rclause);
+			break;
+		}
+	}
+
+	/*
+	 * Construct sub-query structures
+	 */
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid			relid = lfirst_oid(cell1);
+		Node	   *qual = lfirst(cell2);
+		int			flags = lfirst_int(cell3);
+
+		subqry = make_pseudo_subquery(relid_head, relid, qual, flags,
+									  targetList, rte, rclause);
+		if (cell1 == list_head(rls_relids))
+		{
+			Assert(rte->relid == relid);
+			Assert(rte->inh == true || list_length(rls_relids) == 1);
+
+			rte->relid = InvalidOid;
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = subqry;
+			rte->security_barrier = true;
+
+			/* no permission checks are needed to subquery itself */
+			rte->requiredPerms = 0;
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->modifiedCols = NULL;
+
+			rte->alias = NULL;
+			rte->eref = makeAlias(get_rel_name(relid),
+								  copyObject(colnameList));
+			if (list_length(rls_relids) == 1)
+				rte->inh = false;
+		}
+
+		/*
+		 * RTE's for child relations and AppendRelInfo in case when
+		 * the target relation has its children.
+		 */
+		if (list_length(rls_relids) > 1)
+		{
+			AppendRelInfo  *apinfo;
+			AttrNumber		child_rtindex;
+			ListCell	   *l;
+
+			/*
+			 * XXX - Set up RangeTblEntry for the child relation.
+			 * Note that it does not need to have security_barrier
+			 * attribute, if no row-level security policy is
+			 * configured on.
+			 */
+			subrte = makeNode(RangeTblEntry);
+			subrte->rtekind = RTE_SUBQUERY;
+			subrte->subquery = subqry;
+			if (qual)
+				subrte->security_barrier = true;
+			subrte->alias = NULL;
+			subrte->eref = makeAlias(get_rel_name(relid),
+									 copyObject(colnameList));
+			parse->rtable = lappend(parse->rtable, subrte);
+			child_rtindex = list_length(parse->rtable);
+
+			/*
+			 * reference to inherited children performs as if simple
+			 * UNION ALL operation, so add AppendRelInfo here.
+			 */
+			apinfo = makeNode(AppendRelInfo);
+			apinfo->parent_relid = rtindex;
+			apinfo->child_relid = child_rtindex;
+			foreach (l, targetList)
+			{
+				Var	   *trans_var
+					= makeVarFromTargetEntry(child_rtindex, lfirst(l));
+				apinfo->translated_vars
+					= lappend(apinfo->translated_vars, trans_var);
+			}
+			root->append_rel_list = lappend(root->append_rel_list, apinfo);
+		}
+	}
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+applyRowLevelSecurity(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		has_rowlevel_sec = false;
+
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		List		   *rls_relids;
+		List		   *rls_quals;
+		List		   *rls_flags;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * XXX - In this revision, we have no support for UPDATE / DELETE
+		 * statement, so simply skip it.
+		 */
+		if (rtindex == parse->resultRelation)
+			continue;
+
+		/*
+		 * In case when a row-level security policy was configured on
+		 * the table referenced by this RangeTblEntry or its children,
+		 * it shall be rewritten to sub-query with this policy.
+		 */
+		if (pull_rowlevel_security_policy(root, rte, rtindex,
+										  &rls_relids, &rls_quals, &rls_flags))
+		{
+			expand_rtentry_with_policy(root, rte, rtindex,
+									   rls_relids, rls_quals, rls_flags);
+			has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * Post case handling if one or more relation was replaced to sub-query.
+	 */
+	if (has_rowlevel_sec)
+	{
+		PlanInvalItem  *pi_item;
+
+		/*
+		 * plan should be invalidated if and when userid was changed
+		 * on the executor stage, from planner stage.
+		 */
+		Assert(!OidIsValid(root->glob->planUserId) ||
+			   root->glob->planUserId == GetUserId());
+		root->glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue
+			= GetSysCacheHashValue1(AUTHOID,
+									ObjectIdGetDatum(root->glob->planUserId));
+		root->glob->invalItems = lappend(root->glob->invalItems, pi_item);
+
+		/*
+		 * Since the relation with RLS policy was replaced by a sub-query,
+		 * thus resource number to reference a particular column can be
+		 * also moditifed. If we applied RLS policy on one or more relations,
+		 * varattno of Var node that has referenced the rewritten relation
+		 * needs to be fixed up.
+		 */
+		fixup_varattnos(root);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5894cb0..0a86ce8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2068,6 +2068,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index d1d835b..87423d8 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..19a2255 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -43,6 +43,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2999,6 +3000,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3081,6 +3083,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3090,6 +3101,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0cdd30d..f37586e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4174,6 +4188,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index cdbed20..e781dae 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3867,6 +3867,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3891,7 +3892,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3911,7 +3950,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3947,7 +3987,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3982,7 +4023,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4017,7 +4059,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4053,7 +4096,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4088,7 +4132,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4119,7 +4164,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4145,7 +4191,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4181,7 +4228,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4229,6 +4277,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4271,6 +4320,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12803,6 +12856,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2aa2060..5485101 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -256,6 +256,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 19178b5..2f9de9f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1231,6 +1232,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index af9425a..73c6ff1 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..9afe00a
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(PlannerInfo *root,
+											 Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3bb35f..d13ef32 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..5673e7a
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,753 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (rls_regress_schema.document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (rls_regress_schema.document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (d float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(c);
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  c  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  c  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2
+                     Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  Seq Scan on t3
+(10 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  LockRows
+                     ->  Seq Scan on t1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  LockRows
+                     ->  Seq Scan on t2
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3
+                           Filter: f_leak(c)
+(16 rows)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+           QUERY PLAN            
+---------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t2 t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t3 t1
+               Filter: f_leak(c)
+(8 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+(6 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+           QUERY PLAN           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a <= 2)
+(8 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+          QUERY PLAN           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2 t1
+               Filter: (a = 2)
+         ->  Seq Scan on t3 t1
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 7 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ac29194..f0541c0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 8576a7f..e789395 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a4d1ccd
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,237 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- 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 (d 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 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+301	1	xxx	X
+302	2	yyy	Y
+303	3	zzz	Z
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- reference to system column
+SELECT oid, * FROM t1;
+EXPLAIN (costs off) SELECT * 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(c) FOR SHARE;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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;
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-10-08 14:57                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-10-08 14:57 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

The attached patch is a refreshed version towards the latest master branch,
to fix up patch conflicts.
Here is no other difference from the previous revision.

Thanks,

2012/9/5 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> 2012/9/3 Kohei KaiGai <kaigai@kaigai.gr.jp>:
>> 2012/9/2 Dean Rasheed <dean.a.rasheed@gmail.com>:
>>> On 17 July 2012 05:02, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>>> 2012/7/17 Robert Haas <robertmhaas@gmail.com>:
>>>>> On Sun, Jul 15, 2012 at 5:52 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>>>>> The attached patch is a revised version of row-level security feature.
>>>>>> ...
>>>>>> According to the Robert's comment, I revised the place to inject
>>>>>> applyRowLevelSecurity(). The reason why it needed to patch on
>>>>>> adjust_appendrel_attrs_mutator() was, we handled expansion from
>>>>>> regular relation to sub-query after expand_inherited_tables().
>>>>>> In this revision, it was moved to the head of sub-query planner.
>>>>>>
>>>
>>> Hi,
>>>
>>> I had a quick look at this and spotted a problem - certain types of
>>> query are able to bypass the RLS quals. For example:
>>>
>>> SELECT * FROM (SELECT * FROM foo) foo;
>>>
>>> since the RLS policy doesn't descend into subqueries, and is applied
>>> before they are pulled up into the main query. Similarly for views on
>>> top of tables with RLS, and SRF functions that query a table with RLS
>>> that get inlined.
>>>
>>> Also queries using UNION ALL are vulnerable if they end up being
>>> flattened, for example:
>>>
>>> SELECT * FROM foo UNION ALL SELECT * FROM foo;
>>>
>> Thanks for your comment.
>>
>> Indeed, I missed the case of simple sub-queries and union-all being
>> pulled up into the main query. So, I adjusted the location to invoke
>> applyRowLevelSecurity() between all the pull-up stuff and expanding
>> inherited tables.
>>
>> The attached patch is a fixed and rebased revision for CF:Sep.
>>
> Sorry! I attached incorrect revision. The attached patch is right one.
>
> Thanks,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.ro.v4.patch (118.1K, ../../CADyhKSU+Qwx6qkeELr=MbZ7Lz2Wpoqn9Uihff-OktJ_wogHEpw@mail.gmail.com/2-pgsql-v9.3-row-level-security.ro.v4.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  59 +++
 doc/src/sgml/ref/alter_table.sgml          |  38 ++
 doc/src/sgml/user-manag.sgml               | 123 +++++
 src/backend/access/transam/xact.c          |  12 +
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 241 +++++++++
 src/backend/commands/tablecmds.c           |  64 +++
 src/backend/optimizer/plan/planner.c       |  16 +
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 678 ++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/utils/adt/ri_triggers.c        |  14 +
 src/backend/utils/cache/plancache.c        |  32 ++
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/pg_dump.c                  |  76 ++-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |  20 +-
 src/include/catalog/pg_rowlevelsec.h       |  60 +++
 src/include/nodes/parsenodes.h             |   5 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  31 ++
 src/include/parser/parse_node.h            |   3 +-
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 753 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowlevelsec.sql       | 237 +++++++++
 36 files changed, 2526 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f999190..55898a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 356419e..9255c45 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -808,6 +833,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e7a6606..851f9db 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -144,6 +145,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -173,6 +175,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1764,6 +1767,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2295,6 +2300,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4188,6 +4196,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4327,6 +4338,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index 87d6f02..f0736b1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1229,6 +1230,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2280,6 +2285,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2929,6 +2937,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c80df41..60de76d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..e554f20
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,241 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Indert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+void
+ResetRowLevelSecurity(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 359d478..83ca86b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3108,6 +3112,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7533,6 +7545,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9631,6 +9659,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+		class_form->relhasrowlevelsec = true;
+	else
+		class_form->relhasrowlevelsec = false;
+
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..f9d315e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,13 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 */
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +252,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -351,6 +360,13 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		flatten_simple_union_all(root);
 
 	/*
+	 * Apply row-level security policy of appeared tables, if configured.
+	 * Note that it must be processed after all the simple subqueries or
+	 * union all queries being flatten, but prior to preprocess_rowmarks().
+	 */
+	applyRowLevelSecurity(root);
+
+	/*
 	 * Detect whether any rangetable entries are RTE_JOIN kind; if not, we can
 	 * avoid the expense of doing flatten_join_alias_vars().  Also check for
 	 * outer joins --- if none, we can skip reduce_outer_joins().  And check
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..3a01b0c
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,678 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * pull_rowlevel_security_policy
+ *
+ * This routine tries to pull expression node of row-level security policy
+ * on the target relation, and its children if configured.
+ * If one or more relation has a security policy at least, this function
+ * returns true, or false elsewhere.
+ */
+static bool
+pull_rowlevel_security_policy(PlannerInfo *root,
+							  RangeTblEntry *rte,
+							  Index rtindex,
+							  List **rls_relids,
+							  List **rls_quals,
+							  List **rls_flags)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *qual_list = NIL;
+	List		   *flag_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == root->parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, root->parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr   *qual = NULL;
+		int		flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = copyObject(rlsdesc->rlsqual);
+			if (rlsdesc->rlshassublinks)
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+		}
+
+		/*
+		 * Also, ask extensions whether they want to apply their own
+		 * row-level security policy. If both built-in and extension
+		 * has their own policy, it shall be merged.
+		 */
+		if (rowlevel_security_hook)
+		{
+			List   *qual_list;
+
+			qual_list = (*rowlevel_security_hook)(root, rel);
+			if (qual_list != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *)qual_list))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					qual_list = lappend(qual_list, qual);
+
+				if (list_length(qual_list) == 1)
+					qual = (Expr *)list_head(qual_list);
+				else
+					qual = makeBoolExpr(AND_EXPR, qual_list, -1);
+			}
+		}
+
+		qual_list = lappend(qual_list, qual);
+		if (qual)
+			result = true;
+		flag_list = lappend_int(flag_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	/*
+	 * Inform the caller list of relation Oid, qualifier of row-level
+	 * security policy and its flag, if one or more target relations
+	 * have its row-level security policy. Elsewhere, release it.
+	 */
+	if (result)
+	{
+		*rls_relids = relid_list;
+		*rls_quals = qual_list;
+		*rls_flags = flag_list;
+	}
+	else
+	{
+		list_free(relid_list);
+		list_free(qual_list);
+		list_free(flag_list);
+	}
+	return result;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	PlannerInfo *root;
+	int		varlevelsup;
+} fixup_var_context;
+
+static bool
+fixup_var_references_walker(Node *node, fixup_var_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		if (rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			List       *targetList = rte->subquery->targetList;
+			ListCell   *cell;
+
+			foreach (cell, targetList)
+			{
+				TargetEntry    *subtle = lfirst(cell);
+
+				if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+					 var->varattno == InvalidAttrNumber) ||
+					(IsA(subtle->expr, Var) &&
+					 var->varattno == ((Var *)(subtle->expr))->varattno))
+				{
+					var->varattno = subtle->resno;
+					return false;
+				}
+			}
+			elog(ERROR, "invalid varattno %d", var->varattno);
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_var_references_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_var_references_walker,
+								  (void *) context);
+}
+
+static void
+fixup_varattnos(PlannerInfo *root)
+{
+	fixup_var_context   context;
+
+	/*
+	 * Fixup Var->varattno that references the sub-queries originated from
+	 * regular relations with RLS policy.
+	 */
+	context.root = root;
+	context.varlevelsup = 0;
+
+	query_tree_walker(root->parse,
+					  fixup_var_references_walker,
+					  (void *) &context, 0);
+}
+
+/*
+ * make_pseudo_column
+ *
+ * make a TargetEntry object which references a particular column of
+ * the underlying table.
+ */
+static TargetEntry *
+make_pseudo_column(Oid relid_head, Oid relid, AttrNumber attnum)
+{
+	Form_pg_attribute attform;
+	HeapTuple	tuple;
+	Var		   *var;
+	char	   *resname;
+
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(relid_head);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		return makeTargetEntry((Expr *) r, -1, get_rel_name(relid), false);
+	}
+
+	tuple = SearchSysCache2(ATTNUM,
+							ObjectIdGetDatum(relid),
+							Int16GetDatum(attnum));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 attnum, relid);
+	attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+	var = makeVar((Index) 1,
+				  attform->attnum,
+				  attform->atttypid,
+				  attform->atttypmod,
+				  InvalidOid,
+				  0);
+	resname = pstrdup(NameStr(attform->attname));
+
+	ReleaseSysCache(tuple);
+
+	return makeTargetEntry((Expr *)var, -1, resname, false);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(Oid relid_head,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 List *targetList,
+					 RangeTblEntry *rte,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	List		   *colnameList = NIL;
+	ListCell	   *cell;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = rte->requiredPerms;
+	subrte->selectedCols = NULL;
+	subrte->modifiedCols = NULL;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry	   *oldtle = lfirst(cell);
+		TargetEntry	   *subtle;
+		AttrNumber		attnum;
+
+		if (IsA(oldtle->expr, ConvertRowtypeExpr))
+			attnum = InvalidAttrNumber;
+		else
+		{
+			Assert(IsA(oldtle->expr, Var));
+
+			attnum = get_attnum(relid, oldtle->resname);
+			if (attnum == InvalidAttrNumber)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+					errmsg("column \"%s\" of relation \"%s\" does not exist",
+								oldtle->resname, get_rel_name(relid))));
+		}
+		subtle = make_pseudo_column(relid_head, relid, attnum);
+		subtle->resno = oldtle->resno;
+		subqry->targetList = lappend(subqry->targetList, subtle);
+
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+		attnum -= FirstLowInvalidHeapAttributeNumber;
+		subrte->selectedCols = bms_add_member(rte->selectedCols, attnum);
+	}
+	subrte->eref = makeAlias(get_rel_name(relid), colnameList);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * This routine expand reference to the given RangeTblEntry by a sub-query
+ * which simply references the target relation with the qualifier of row-
+ * level security policy.
+ */
+static void
+expand_rtentry_with_policy(PlannerInfo *root,
+						   RangeTblEntry *rte,
+						   Index rtindex,
+						   List *rls_relids,
+						   List *rls_quals,
+						   List *rls_flags)
+{
+	Query		   *parse = root->parse;
+	Oid				relid_head;
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	List		   *targetList;
+	List		   *colnameList;
+	Bitmapset	   *attr_used;
+	AttrNumber		attnum;
+	RowMarkClause  *rclause;
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+	ListCell	   *cell3;
+	ListCell	   *lc;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 0);
+
+	/*
+	 * Construct a target-entry list
+	 */
+	targetList = NIL;
+	colnameList = NIL;
+	relid_head = linitial_oid(rls_relids);
+	attr_used = bms_union(rte->selectedCols,
+						  rte->modifiedCols);
+	while ((attnum = bms_first_member(attr_used)) >= 0)
+	{
+		attnum += FirstLowInvalidHeapAttributeNumber;
+
+		subtle = make_pseudo_column(relid_head, relid_head, attnum);
+		subtle->resno = list_length(targetList) + 1;
+
+		targetList = lappend(targetList, subtle);
+		colnameList = lappend(colnameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	bms_free(attr_used);
+
+	/*
+	 * Push-down row-level lock of the target relation, since sub-query
+	 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+	 */
+	rclause = NULL;
+	foreach (lc, parse->rowMarks)
+	{
+		if (((RowMarkClause *) lfirst(lc))->rti == rtindex)
+		{
+			rclause = lfirst(lc);
+			parse->rowMarks = list_delete(parse->rowMarks, rclause);
+			break;
+		}
+	}
+
+	/*
+	 * Construct sub-query structures
+	 */
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid			relid = lfirst_oid(cell1);
+		Node	   *qual = lfirst(cell2);
+		int			flags = lfirst_int(cell3);
+
+		subqry = make_pseudo_subquery(relid_head, relid, qual, flags,
+									  targetList, rte, rclause);
+		if (cell1 == list_head(rls_relids))
+		{
+			Assert(rte->relid == relid);
+			Assert(rte->inh == true || list_length(rls_relids) == 1);
+
+			rte->relid = InvalidOid;
+			rte->rtekind = RTE_SUBQUERY;
+			rte->subquery = subqry;
+			rte->security_barrier = true;
+
+			/* no permission checks are needed to subquery itself */
+			rte->requiredPerms = 0;
+			rte->checkAsUser = InvalidOid;
+			rte->selectedCols = NULL;
+			rte->modifiedCols = NULL;
+
+			rte->alias = NULL;
+			rte->eref = makeAlias(get_rel_name(relid),
+								  copyObject(colnameList));
+			if (list_length(rls_relids) == 1)
+				rte->inh = false;
+		}
+
+		/*
+		 * RTE's for child relations and AppendRelInfo in case when
+		 * the target relation has its children.
+		 */
+		if (list_length(rls_relids) > 1)
+		{
+			AppendRelInfo  *apinfo;
+			AttrNumber		child_rtindex;
+			ListCell	   *l;
+
+			/*
+			 * XXX - Set up RangeTblEntry for the child relation.
+			 * Note that it does not need to have security_barrier
+			 * attribute, if no row-level security policy is
+			 * configured on.
+			 */
+			subrte = makeNode(RangeTblEntry);
+			subrte->rtekind = RTE_SUBQUERY;
+			subrte->subquery = subqry;
+			if (qual)
+				subrte->security_barrier = true;
+			subrte->alias = NULL;
+			subrte->eref = makeAlias(get_rel_name(relid),
+									 copyObject(colnameList));
+			parse->rtable = lappend(parse->rtable, subrte);
+			child_rtindex = list_length(parse->rtable);
+
+			/*
+			 * reference to inherited children performs as if simple
+			 * UNION ALL operation, so add AppendRelInfo here.
+			 */
+			apinfo = makeNode(AppendRelInfo);
+			apinfo->parent_relid = rtindex;
+			apinfo->child_relid = child_rtindex;
+			foreach (l, targetList)
+			{
+				Var	   *trans_var
+					= makeVarFromTargetEntry(child_rtindex, lfirst(l));
+				apinfo->translated_vars
+					= lappend(apinfo->translated_vars, trans_var);
+			}
+			root->append_rel_list = lappend(root->append_rel_list, apinfo);
+		}
+	}
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It tries to apply row-level security policy of the relation.
+ * If and when a particular policy is configured on the referenced
+ * relation, it shall be replaced by a sub-query with security-barrier flag;
+ * that references the relation with row-level security policy.
+ * In the result, all users can see is rows of the relation that satisfies
+ * the condition supplied as security policy.
+ */
+void
+applyRowLevelSecurity(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	ListCell   *cell;
+	Index		rtindex;
+	bool		has_rowlevel_sec = false;
+
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * No need to apply row-level security on sub-query being originated
+	 * from regular relation with RLS policy any more.
+	 */
+	if (parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+		return;
+
+	rtindex = 0;
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		List		   *rls_relids;
+		List		   *rls_quals;
+		List		   *rls_flags;
+
+		rtindex++;
+
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * XXX - In this revision, we have no support for UPDATE / DELETE
+		 * statement, so simply skip it.
+		 */
+		if (rtindex == parse->resultRelation)
+			continue;
+
+		/*
+		 * In case when a row-level security policy was configured on
+		 * the table referenced by this RangeTblEntry or its children,
+		 * it shall be rewritten to sub-query with this policy.
+		 */
+		if (pull_rowlevel_security_policy(root, rte, rtindex,
+										  &rls_relids, &rls_quals, &rls_flags))
+		{
+			expand_rtentry_with_policy(root, rte, rtindex,
+									   rls_relids, rls_quals, rls_flags);
+			has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * Post case handling if one or more relation was replaced to sub-query.
+	 */
+	if (has_rowlevel_sec)
+	{
+		PlanInvalItem  *pi_item;
+
+		/*
+		 * plan should be invalidated if and when userid was changed
+		 * on the executor stage, from planner stage.
+		 */
+		Assert(!OidIsValid(root->glob->planUserId) ||
+			   root->glob->planUserId == GetUserId());
+		root->glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue
+			= GetSysCacheHashValue1(AUTHOID,
+									ObjectIdGetDatum(root->glob->planUserId));
+		root->glob->invalItems = lappend(root->glob->invalItems, pi_item);
+
+		/*
+		 * Since the relation with RLS policy was replaced by a sub-query,
+		 * thus resource number to reference a particular column can be
+		 * also moditifed. If we applied RLS policy on one or more relations,
+		 * varattno of Var node that has referenced the rewritten relation
+		 * needs to be fixed up.
+		 */
+		fixup_varattnos(root);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e4ff76e..a3d445d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2127,6 +2127,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index d1d835b..87423d8 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..19a2255 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -43,6 +43,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2999,6 +3000,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3081,6 +3083,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3090,6 +3101,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0cdd30d..f37586e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4174,6 +4188,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9920d96..c23a324 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3867,6 +3867,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3891,7 +3892,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3911,7 +3950,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3947,7 +3987,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3982,7 +4023,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4017,7 +4059,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4053,7 +4096,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4088,7 +4132,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4119,7 +4164,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4145,7 +4191,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4181,7 +4228,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4229,6 +4277,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4271,6 +4320,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12803,6 +12856,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2aa2060..5485101 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -256,6 +256,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 09b15e7..4118794 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1232,6 +1233,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2b2742d..f20923a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..9afe00a
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(PlannerInfo *root,
+											 Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3bb35f..d13ef32 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..1658806
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,753 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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 account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse browse_1  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse browse_1  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- just a disturbing factor
+GRANT ALL ON t1 TO public;
+COPY t1 FROM stdin WITH (oids);
+CREATE TABLE t2 (d float) INHERITS (t1);
+COPY t2 FROM stdin WITH (oids);
+CREATE TABLE t3 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 WHERE f_leak(c);
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  c  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  c  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  Seq Scan on t3 t3_1
+(10 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1 t1_1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2 t2_1
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3 t3_1
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+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
+ a |  c  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.c)
+               ->  LockRows
+                     ->  Seq Scan on t1 t1_1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.c)
+               ->  LockRows
+                     ->  Seq Scan on t2 t2_1
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3 t3_1
+                           Filter: f_leak(c)
+(16 rows)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1 WHERE f_leak(c);
+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 |  c  
+---+-----
+ 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(c);
+           QUERY PLAN            
+---------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: f_leak(c)
+         ->  Seq Scan on t2
+               Filter: f_leak(c)
+         ->  Seq Scan on t3
+               Filter: f_leak(c)
+(8 rows)
+
+-- plan cache should be invalidated
+EXECUTE p1(2);
+ a |  c  
+---+-----
+ 1 | aaa
+ 2 | bbb
+ 1 | abc
+ 2 | bcd
+ 1 | xxx
+ 2 | yyy
+(6 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+           QUERY PLAN           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2
+               Filter: (a <= 2)
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+PREPARE p2(int) AS SELECT * FROM t1 WHERE a = $1;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | bcd
+ 2 | yyy
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+          QUERY PLAN           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2
+               Filter: (a = 2)
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  c  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 7 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+DROP USER rls_regress_user0;
+DROP USER rls_regress_user1;
+DROP USER rls_regress_user2;
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f1a72f7..e0cef62 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate alter_generic
+test: privileges rowlevelsec security_label collate alter_generic
 
 test: misc
 # rules cannot run concurrently with any test that creates a view
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index f2813fb..1317015 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a4d1ccd
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,237 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE t1 (a int, b text, c text) WITH OIDS;
+ALTER TABLE t1 DROP COLUMN b;    -- 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 (d 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 (e text) INHERITS (t1);
+COPY t3 FROM stdin WITH (oids);
+301	1	xxx	X
+302	2	yyy	Y
+303	3	zzz	Z
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+
+SELECT * FROM t1;
+EXPLAIN (costs off) SELECT * FROM t1;
+
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- reference to system column
+SELECT oid, * FROM t1;
+EXPLAIN (costs off) SELECT * 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(c) FOR SHARE;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c) FOR SHARE;
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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;
+SELECT * FROM t1 WHERE f_leak(c);
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(c);
+
+-- 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;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-10-08 18:33                                     ` Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Dean Rasheed @ 2012-10-08 18:33 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On 8 October 2012 15:57, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> The attached patch is a refreshed version towards the latest master branch,
> to fix up patch conflicts.
> Here is no other difference from the previous revision.
>
> Thanks,
>

I had another look at this over the weekend and I found couple of
additional problems (test cases attached):

1). It is possible to define a RLS qual that refers back to the table
that it's defined on, in such a way that causes infinite recursion in
the planner, giving "ERROR:  stack depth limit exceeded". I think it
would be preferable to trap this and report a more meaningful error
back to the user, along similar lines to a self-referencing view.

2). In other cases it is possible to define a RLS qual that refers to
another table with a RLS qual in such a way that the second table's
RLS qual is not checked, thus allowing a user to bypass the security
check.

3). If a RLS qual refers to a view it errors, since the RLS quals are
added after rule expansion, and so the view is not rewritten.

To me this suggests that perhaps the expansion of RLS quals should be
done in the rewriter. I've not thought that through in any detail, but
ISTM that a RIR rule could add a table with a RLS qual, and a RLS qual
could add a relation with a RIR rule that needs expanding, and so the
2 need to be processed together. This could also make use of the
existing recursion-checking code in the rewriter.

Regards,
Dean

Attachments:

  [application/octet-stream] test.sql (1.3K, ../../CAEZATCUz7+QVudWbgnSrv-KbHRPq33Z07vdv1eYn+cDRMbazzQ@mail.gmail.com/2-test.sql)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2012-10-08 19:49                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-10-08 19:49 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/10/8 Dean Rasheed <dean.a.rasheed@gmail.com>:
> On 8 October 2012 15:57, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> The attached patch is a refreshed version towards the latest master branch,
>> to fix up patch conflicts.
>> Here is no other difference from the previous revision.
>>
>> Thanks,
>>
>
> I had another look at this over the weekend and I found couple of
> additional problems (test cases attached):
>
> 1). It is possible to define a RLS qual that refers back to the table
> that it's defined on, in such a way that causes infinite recursion in
> the planner, giving "ERROR:  stack depth limit exceeded". I think it
> would be preferable to trap this and report a more meaningful error
> back to the user, along similar lines to a self-referencing view.
>
> 2). In other cases it is possible to define a RLS qual that refers to
> another table with a RLS qual in such a way that the second table's
> RLS qual is not checked, thus allowing a user to bypass the security
> check.
>
> 3). If a RLS qual refers to a view it errors, since the RLS quals are
> added after rule expansion, and so the view is not rewritten.
>
> To me this suggests that perhaps the expansion of RLS quals should be
> done in the rewriter. I've not thought that through in any detail, but
> ISTM that a RIR rule could add a table with a RLS qual, and a RLS qual
> could add a relation with a RIR rule that needs expanding, and so the
> 2 need to be processed together. This could also make use of the
> existing recursion-checking code in the rewriter.
>
Thanks for your checks. I missed some cases that you suggested.

The reason why we need to put RLS expansion at planner stage is
requirement towards plan cache invalidation. Due to special case
handling for superuser, plan cache has to be invalidated if user-id
to run executor was switched since planner stage. The planner shall
be invoked again, but not rewritter, on its invalidation.

Probably, it make sense to invoke rewriter's logic to solve RLS
policy from planner stage (that allows plan-cache invalidation).
Let me investigate the code of rewriter.

Best regards,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-10-15 20:19                                         ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-10-15 20:19 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

The revised patch fixes the problem that Daen pointed out.

In case when row-level security policy contains SubLink node,
it become to call the rewriter to expand views being contained
within SubLink, and also append qualifier of security policy
onto underlying tables. It enables to apply configured policy
on nested relations also, as if top-level ones.
In addition, I added a check for infinite recursion when two
different tables have row-level policy that references each
other.

So, for example, self-recursion RLS shall be prevented.
  postgres=> ALTER TABLE foo SET ROW LEVEL SECURITY (a < (SELECT
max(a) FROM foo));
  ALTER TABLE
  postgres=> SELECT * FROM foo;
  ERROR:  infinite recursion detected for relation "foo"

It shows RLS policy is recursively applied.
  postgres=> ALTER TABLE foo SET ROW LEVEL SECURITY (a > 0);
  ALTER TABLE
  postgres=> ALTER TABLE bar SET ROW LEVEL SECURITY
                    (EXISTS(SELECT 1 FROM foo WHERE foo.a = bar.a));
  ALTER TABLE
  postgres=> EXPLAIN SELECT * FROM bar;
                                           QUERY PLAN
  --------------------------------------------------------------------------------------------
   Hash Join  (cost=44.95..111.95 rows=1200 width=4)
     Hash Cond: (bar.a = foo.a)
     ->  Seq Scan on bar  (cost=0.00..34.00 rows=2400 width=4)
     ->  Hash  (cost=42.45..42.45 rows=200 width=4)
           ->  HashAggregate  (cost=40.45..42.45 rows=200 width=4)
                 ->  Bitmap Heap Scan on foo  (cost=10.45..30.45
rows=800 width=4)
                       Recheck Cond: (a > 0)
                       ->  Bitmap Index Scan on foo_pkey
(cost=0.00..10.25 rows=800 width=0)
                             Index Cond: (a > 0)
  (9 rows)

Even if RLS policy contains a view, it works fine.
  postgres=> CREATE VIEW foo_v AS SELECT * FROM foo;
  CREATE VIEW
  postgres=> ALTER TABLE bar SET ROW LEVEL SECURITY
                    (a IN (SELECT * FROM foo_v));
  ALTER TABLE
  postgres=> EXPLAIN SELECT * FROM bar;
                                           QUERY PLAN
  --------------------------------------------------------------------------------------------
   Hash Join  (cost=44.95..111.95 rows=1200 width=4)
     Hash Cond: (bar.a = foo.a)
     ->  Seq Scan on bar  (cost=0.00..34.00 rows=2400 width=4)
     ->  Hash  (cost=42.45..42.45 rows=200 width=4)
           ->  HashAggregate  (cost=40.45..42.45 rows=200 width=4)
                 ->  Bitmap Heap Scan on foo  (cost=10.45..30.45
rows=800 width=4)
                       Recheck Cond: (a > 0)
                       ->  Bitmap Index Scan on foo_pkey
(cost=0.00..10.25 rows=800 width=0)
                             Index Cond: (a > 0)
  (9 rows)

Thanks,

2012/10/8 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> 2012/10/8 Dean Rasheed <dean.a.rasheed@gmail.com>:
>> On 8 October 2012 15:57, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>> The attached patch is a refreshed version towards the latest master branch,
>>> to fix up patch conflicts.
>>> Here is no other difference from the previous revision.
>>>
>>> Thanks,
>>>
>>
>> I had another look at this over the weekend and I found couple of
>> additional problems (test cases attached):
>>
>> 1). It is possible to define a RLS qual that refers back to the table
>> that it's defined on, in such a way that causes infinite recursion in
>> the planner, giving "ERROR:  stack depth limit exceeded". I think it
>> would be preferable to trap this and report a more meaningful error
>> back to the user, along similar lines to a self-referencing view.
>>
>> 2). In other cases it is possible to define a RLS qual that refers to
>> another table with a RLS qual in such a way that the second table's
>> RLS qual is not checked, thus allowing a user to bypass the security
>> check.
>>
>> 3). If a RLS qual refers to a view it errors, since the RLS quals are
>> added after rule expansion, and so the view is not rewritten.
>>
>> To me this suggests that perhaps the expansion of RLS quals should be
>> done in the rewriter. I've not thought that through in any detail, but
>> ISTM that a RIR rule could add a table with a RLS qual, and a RLS qual
>> could add a relation with a RIR rule that needs expanding, and so the
>> 2 need to be processed together. This could also make use of the
>> existing recursion-checking code in the rewriter.
>>
> Thanks for your checks. I missed some cases that you suggested.
>
> The reason why we need to put RLS expansion at planner stage is
> requirement towards plan cache invalidation. Due to special case
> handling for superuser, plan cache has to be invalidated if user-id
> to run executor was switched since planner stage. The planner shall
> be invoked again, but not rewritter, on its invalidation.
>
> Probably, it make sense to invoke rewriter's logic to solve RLS
> policy from planner stage (that allows plan-cache invalidation).
> Let me investigate the code of rewriter.
>
> Best regards,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.ro.v5.patch (133.7K, ../../CADyhKSUFcMcwup1=ydvwghiy21Vn_OGzO-rX-VCBO1CSDNNTMQ@mail.gmail.com/2-pgsql-v9.3-row-level-security.ro.v5.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  59 ++
 doc/src/sgml/ref/alter_table.sgml          |  38 ++
 doc/src/sgml/user-manag.sgml               | 123 ++++
 src/backend/access/transam/xact.c          |  12 +
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 241 +++++++
 src/backend/commands/tablecmds.c           |  64 ++
 src/backend/optimizer/plan/planner.c       |  12 +
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 977 +++++++++++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  14 +
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/pg_dump.c                  |  76 ++-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |  20 +-
 src/include/catalog/pg_rowlevelsec.h       |  60 ++
 src/include/nodes/parsenodes.h             |   5 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  31 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 853 +++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowlevelsec.sql       | 273 ++++++++
 38 files changed, 2974 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f999190..55898a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 356419e..9255c45 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -808,6 +833,19 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured,
+        however, unavailable to include sub-query right now.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c283e07 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,127 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Right now, row-level security feature has several limitation,
+   although these shall be improved in the future version.
+
+   Row-level security policy is not applied to
+   <command>UPDATE</command> or <command>DELETE</command>
+   commands in this revision, even though it should be
+   supported soon.
+
+   Row-level security policy is not applied to rows to be inserted
+   and newer revision of updated rows, thus, it requires to
+   define before-row-insert or before-row-update trigger to check
+   whether the row's contents satisfies the policy individually.
+
+   Although it is not a specific matter in row-level security,
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it may cause unpredicated behavior
+   if and when <literal>current_user</literal> is used as
+   a part of row-level security policy.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e7a6606..851f9db 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -35,6 +35,7 @@
 #include "executor/spi.h"
 #include "libpq/be-fsstubs.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "pgstat.h"
 #include "replication/walsender.h"
 #include "replication/syncrep.h"
@@ -144,6 +145,7 @@ typedef struct TransactionStateData
 	int			maxChildXids;	/* allocated size of childXids[] */
 	Oid			prevUser;		/* previous CurrentUserId setting */
 	int			prevSecContext; /* previous SecurityRestrictionContext */
+	RowLevelSecMode	prevRowLevelSecMode;	/* previous RLS-mode setting */
 	bool		prevXactReadOnly;		/* entry-time xact r/o state */
 	bool		startedInRecovery;		/* did we start in recovery? */
 	struct TransactionStateData *parent;		/* back link to parent */
@@ -173,6 +175,7 @@ static TransactionStateData TopTransactionStateData = {
 	0,							/* allocated size of childXids[] */
 	InvalidOid,					/* previous CurrentUserId setting */
 	0,							/* previous SecurityRestrictionContext */
+	RowLevelSecModeEnabled,		/* previous RowLevelSecMode setting */
 	false,						/* entry-time xact r/o state */
 	false,						/* startedInRecovery */
 	NULL						/* link to parent state block */
@@ -1764,6 +1767,8 @@ StartTransaction(void)
 	/* SecurityRestrictionContext should never be set outside a transaction */
 	Assert(s->prevSecContext == 0);
 
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
+
 	/*
 	 * initialize other subsystems for new transaction
 	 */
@@ -2295,6 +2300,9 @@ AbortTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Also, mode setting of row-level security should be restored. */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * do abort processing
 	 */
@@ -4188,6 +4196,9 @@ AbortSubTransaction(void)
 	 */
 	SetUserIdAndSecContext(s->prevUser, s->prevSecContext);
 
+	/* Reset row-level security mode */
+	setRowLevelSecurityMode(s->prevRowLevelSecMode);
+
 	/*
 	 * We can skip all this stuff if the subxact failed before creating a
 	 * ResourceOwner...
@@ -4327,6 +4338,7 @@ PushTransaction(void)
 	s->state = TRANS_DEFAULT;
 	s->blockState = TBLOCK_SUBBEGIN;
 	GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
+	s->prevRowLevelSecMode = getRowLevelSecurityMode();
 	s->prevXactReadOnly = XactReadOnly;
 
 	CurrentTransactionState = s;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index 87d6f02..f0736b1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1229,6 +1230,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2280,6 +2285,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2929,6 +2937,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c80df41..60de76d 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..e554f20
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,241 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Indert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+void
+ResetRowLevelSecurity(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of Row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 359d478..83ca86b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -384,6 +385,7 @@ static void ATExecDropInherit(Relation rel, RangeVar *parent, LOCKMODE lockmode)
 static void drop_parent_dependency(Oid relid, Oid refclassid, Oid refobjid);
 static void ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode);
 static void ATExecDropOf(Relation rel, LOCKMODE lockmode);
+static void ATExecSetRowLevelSecurity(Relation relation, Node *clause);
 static void ATExecGenericOptions(Relation rel, List *options);
 
 static void copy_relation_data(SMgrRelation rel, SMgrRelation dst,
@@ -2746,6 +2748,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3108,6 +3112,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3383,6 +3389,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			ATExecSetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7533,6 +7545,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
@@ -9631,6 +9659,42 @@ ATExecDropOf(Relation rel, LOCKMODE lockmode)
 }
 
 /*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+static void
+ATExecSetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	if (clause != NULL)
+		SetRowLevelSecurity(relation, clause);
+	else
+		ResetRowLevelSecurity(relation);
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+		class_form->relhasrowlevelsec = true;
+	else
+		class_form->relhasrowlevelsec = false;
+
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
+
+/*
  * ALTER FOREIGN TABLE <name> OPTIONS (...)
  */
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..ad8b9ad 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,16 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
+	/*
+	 * XXX - a valid user-id shall be set on planUserId later, if constructed
+	 * plan assumes being executed under privilege of a particular user-id.
+	 * Elsewhere, keep InvalidOid; that means the constructed plan is portable
+	 * for any users.
+	 * Please also note that it is also the reason why row-level security
+	 * needs to be placed on top of planner stage, instead of rewriter.
+	 */
+	applyRowLevelSecurity(glob, parse);
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..664c993
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,977 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/* current performing mode of row-level security policy */
+static RowLevelSecMode	rowlevel_security_mode = RowLevelSecModeEnabled;
+
+static bool	apply_subquery_rowlevelsec(Query *parse, List *active_relids);
+
+/*
+ * getRowLevelSecurityMode / setRowLevelSecurityMode
+ *
+ * These functions allow to get or set current performing mode of row-
+ * level security feature. It enables to disable this feature temporarily
+ * for some cases in which row-level security prevent correct behavior
+ * such as foreign-key checks to prohibit update of PKs being referenced
+ * by others.
+ * The caller must ensure the saved previous mode shall be restored, but
+ * no need to care about cases when an error would be raised.
+ */
+RowLevelSecMode
+getRowLevelSecurityMode(void)
+{
+	return rowlevel_security_mode;
+}
+
+void
+setRowLevelSecurityMode(RowLevelSecMode new_mode)
+{
+	rowlevel_security_mode = new_mode;
+}
+
+/*
+ * check_and_fixup_sublink
+ *
+ * This routine calls query rewriter to handle sub-query of sub-link
+ * within row-level security policy, and ensure it does not contain
+ * any infinite reference loop.
+ */
+typedef struct {
+	bool	has_rowlevel_sec;
+	bool	exec_query_rewriter;
+	List   *active_relids;
+} fixup_sublink_context;
+
+static bool
+fixup_sublink_walker(Node *node, fixup_sublink_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, SubLink))
+	{
+		SubLink	   *sl = (SubLink *) node;
+
+		Assert(IsA(sl->subselect, Query));
+
+		if (context->exec_query_rewriter)
+			QueryRewriteExpr((Node *) sl, context->active_relids);
+
+		if (apply_subquery_rowlevelsec((Query *)sl->subselect,
+									   context->active_relids))
+			context->has_rowlevel_sec = true;
+	}
+	return expression_tree_walker(node, fixup_sublink_walker,
+								  (void *) context);
+}
+
+/*
+ * make_pseudo_subquery
+ *
+ * This routine makes a sub-query that references the target relation
+ * with given row-level security policy. This sub-query shall have
+ * security_barrier attribute to prevent unexpected push-down.
+ */
+static Query *
+make_pseudo_subquery(RangeTblEntry *rte,
+					 Oid relid,
+					 Node *qual,
+					 int flags,
+					 RowMarkClause *rclause)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+
+	subqry = makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = makeNode(RangeTblEntry);
+	subrte->rtekind = RTE_RELATION;
+	subrte->relid = relid;
+	subrte->relkind = get_rel_relkind(relid);
+	subrte->inFromCl = true;
+	subrte->requiredPerms = 0;
+	if (rte->relid == relid)
+	{
+		subrte->requiredPerms = rte->requiredPerms;
+		subrte->selectedCols = bms_copy(rte->selectedCols);
+		subrte->modifiedCols = bms_copy(rte->modifiedCols);
+	}
+	else
+	{
+		/*
+		 * selectedCols and modifiedCols should be adjusted
+		 * according to child table definition, because it
+		 * may have original attribute-number in comparison
+		 * with the parent table.
+		 */
+		AttrNumber	attidx;
+		AttrNumber	attnum;
+		char	   *attname;
+		Bitmapset  *attrs_used = bms_union(rte->selectedCols,
+										   rte->modifiedCols);
+		while ((attidx = bms_first_member(attrs_used)) >= 0)
+		{
+			attnum = attidx + FirstLowInvalidHeapAttributeNumber;
+			if (attnum != InvalidAttrNumber)
+			{
+				attname = get_relid_attribute_name(rte->relid, attnum);
+				attnum = get_attnum(relid, attname);
+				if (attnum == InvalidAttrNumber)
+					elog(ERROR, "Not exist column \"%s\" of relation \"%s\"",
+						 attname, get_rel_name(relid));
+				pfree(attname);
+			}
+			attnum -= FirstLowInvalidHeapAttributeNumber;
+			if (bms_is_member(attidx, rte->selectedCols))
+				bms_add_member(subrte->selectedCols, attnum);
+			if (bms_is_member(attidx, rte->modifiedCols))
+				bms_add_member(subrte->modifiedCols, attnum);
+		}
+	}
+	subrte->alias = makeAlias(get_rel_name(relid), NIL);
+	subrte->eref = makeAlias(get_rel_name(relid), NIL);
+
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), qual);
+
+	if (rclause)
+	{
+		RowMarkClause  *rclause_sub;
+
+		rclause_sub = copyObject(rclause);
+		rclause_sub->rti = 1;
+
+		subqry->rowMarks = list_make1(rclause_sub);
+		subqry->hasForUpdate = true;
+	}
+	return subqry;
+}
+
+/*
+ * append_pseudo_column
+ *
+ * this routine construct a TargetEntry object that reference a certain
+ * column of the underlying table, then append it on targetList of the
+ * given RangeTblEntry (should be RTE_SUBQUERY).
+ */
+static TargetEntry *
+append_pseudo_column(RangeTblEntry *rte, Oid base_relid, AttrNumber attnum)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Value		   *colname;
+
+	Assert(rte->rtekind == RTE_SUBQUERY);
+	Assert(list_length(rte->subquery->rtable) == 1);
+	Assert(rt_fetch(1, rte->subquery->rtable)->rtekind == RTE_RELATION);
+
+	subqry = rte->subquery;
+	subrte = rt_fetch(1, subqry->rtable);
+
+	/* Special case for whole-row-reference is required */
+	if (attnum == InvalidAttrNumber)
+	{
+		ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
+
+		r->arg = (Expr *) makeVar((Index) 1,
+								  InvalidAttrNumber,
+								  get_rel_type_id(subrte->relid),
+								  -1,
+								  InvalidOid,
+								  0);
+		r->resulttype = get_rel_type_id(base_relid);
+		r->convertformat = COERCE_IMPLICIT_CAST;
+		r->location = -1;
+
+		subtle = makeTargetEntry((Expr *) r,
+								 list_length(subqry->targetList) + 1,
+								 get_rel_name(subrte->relid),
+								 false);
+	}
+	else
+	{
+		Form_pg_attribute attform;
+		HeapTuple	tuple;
+		Var		   *var;
+
+		/*
+		 * If the TargetEntry is not for base relaion, we need to
+		 * adjust attribute number to be used.
+		 */
+		if (attnum > 0 && subrte->relid != base_relid)
+		{
+			char   *attname = get_relid_attribute_name(base_relid, attnum);
+
+			attnum = get_attnum(subrte->relid, attname);
+			if (attnum == InvalidAttrNumber)
+				elog(ERROR, "column \"%s\" of relation \"%s\" does not exist",
+					 attname, get_rel_name(subrte->relid));
+			pfree(attname);
+		}
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for column %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		subtle = makeTargetEntry((Expr *)var,
+								 list_length(subqry->targetList) + 1,
+								 pstrdup(NameStr(attform->attname)),
+								 false);
+		ReleaseSysCache(tuple);
+	}
+	subqry->targetList = lappend(subqry->targetList, subtle);
+
+	colname = makeString(pstrdup(subtle->resname));
+	rte->eref->colnames = lappend(rte->eref->colnames, colname);
+
+	return subtle;
+}
+
+/*
+ * lookup_pseudo_column
+ *
+ * This routine looks up attribute number of pseudo column being associated
+ * with the given attribute number of the underlying table.
+ * If not in target-list of the sub-query, it append a new target-entry.
+ */
+static AttrNumber
+lookup_pseudo_column(RangeTblEntry *rte, AttrNumber attnum)
+{
+	Query		   *subqry = rte->subquery;
+	ListCell	   *cell;
+	TargetEntry	   *subtle;
+	Oid				base_relid;
+
+	Assert(rte->rtekind == RTE_SUBQUERY);
+	Assert(list_length(subqry->rtable) == 1);
+	Assert(rt_fetch(1, subqry->rtable)->rtekind == RTE_RELATION);
+	base_relid = rt_fetch(1, subqry->rtable)->relid;
+
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+			 attnum == InvalidAttrNumber) ||
+			(IsA(subtle->expr, Var) &&
+			 attnum == ((Var *)subtle->expr)->varattno))
+			return subtle->resno;
+	}
+	subtle = append_pseudo_column(rte, base_relid, attnum);
+
+	return subtle->resno;
+}
+
+/*
+ * fixup_setop_walker
+ *
+ * it appends information about newly added pseudo column on
+ * SetOperationStmt tree of the pseudo sub-query.
+ */
+static void
+fixup_setop_walker(Node *setop, Oid coltypeid, int coltypemod)
+{
+	if (IsA(setop, RangeTblRef))
+		return;
+	else if (IsA(setop, SetOperationStmt))
+	{
+		SetOperationStmt *s = (SetOperationStmt *) setop;
+
+		s->colTypes = lappend_oid(s->colTypes, coltypeid);
+		s->colTypmods = lappend_oid(s->colTypmods, coltypemod);
+		s->colCollations = lappend_oid(s->colCollations, InvalidOid);
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(setop));
+}
+
+/*
+ * lookup_pseudo_column_multi
+ *
+ * This routine looks up attribute number of pseudo column being associated
+ * with the given attribute number of the underlying table.
+ * If not in target-list of the sub-query, it append a new target-entry.
+ * Unlike lookup_pseudo_column, it assumes the given RangeTblEntry has
+ * multiple sub-queries underlying simple table scan.
+ */
+static AttrNumber
+lookup_pseudo_column_multi(RangeTblEntry *rte, AttrNumber attnum)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Value		   *colname;
+	Oid				base_relid;
+	Oid				coltypeid = InvalidOid;
+	int				coltypemod = -1;
+	AttrNumber		resno = -1;
+	char		   *resname = NULL;
+
+	Assert(rte->rtekind == RTE_SUBQUERY);
+	Assert(list_length(rte->subquery->rtable) > 1);
+
+	/*
+	 * First, it looks up target-list of the given sub-query.
+	 */
+	subrte = rt_fetch(1, rte->subquery->rtable);
+	Assert(subrte->rtekind == RTE_SUBQUERY);
+
+	subqry = subrte->subquery;
+	Assert(list_length(subqry->rtable) == 1);
+	Assert(rt_fetch(1, subqry->rtable)->rtekind == RTE_RELATION);
+	base_relid = rt_fetch(1, subqry->rtable)->relid;
+
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry	*tle = lfirst(cell);
+		Var *var = (Var *)tle->expr;
+
+		Assert(IsA(var, Var));
+		Assert(var->varattno <= list_length(subqry->targetList));
+
+		subtle = list_nth(subqry->targetList, var->varattno - 1);
+		if ((IsA(subtle->expr, ConvertRowtypeExpr) &&
+			 attnum == InvalidAttrNumber) ||
+			(IsA(subtle->expr, Var) &&
+			 attnum == ((Var *)subtle->expr)->varattno))
+			return tle->resno;
+	}
+
+	/*
+	 * Second, append a TargetEntry to reference the required attribute
+	 * of the underlying tables. Note that the underlying tables are
+	 * connected with UNION ALL, target entries must have exactly same
+	 * resource number and data type.
+	 */
+	foreach (cell, rte->subquery->rtable)
+	{
+		subrte = (RangeTblEntry *) lfirst(cell);
+		subtle = append_pseudo_column(subrte, base_relid, attnum);
+
+		if (cell == list_head(rte->subquery->rtable))
+		{
+			resno = subtle->resno;
+			resname = pstrdup(subtle->resname);
+
+			if (IsA(subtle->expr, ConvertRowtypeExpr))
+			{
+				ConvertRowtypeExpr *r = (ConvertRowtypeExpr *) subtle->expr;
+
+				coltypeid = r->resulttype;
+				coltypemod = -1;
+			}
+			else if (IsA(subtle->expr, Var))
+			{
+				Var	*v = (Var *) subtle->expr;
+
+				coltypeid = v->vartype;
+				coltypemod = v->vartypmod;
+			}
+			else
+				elog(ERROR, "unexpected node type: %d",
+					 (int)nodeTag(subtle->expr));
+		}
+		else
+			Assert(subtle->resno == resno);
+	}
+	/* fixup SetOperationStmt */
+	fixup_setop_walker(rte->subquery->setOperations, coltypeid, coltypemod);
+
+	/*
+	 * Not only underlying sub-queries, the base sub-qury also need to
+	 * have Var entry within the target list.
+	 */
+	subvar = makeVar((Index) 1,
+					 resno,
+					 coltypeid,
+					 coltypemod,
+					 InvalidOid,
+					 0);
+	subtle = makeTargetEntry((Expr *)subvar, resno, resname, false);
+	rte->subquery->targetList = lappend(rte->subquery->targetList, subtle);
+	colname = makeString(pstrdup(resname));
+	rte->eref->colnames = lappend(rte->eref->colnames, colname);
+
+	Assert(list_length(rte->subquery->targetList) == resno);
+
+	return resno;
+}
+
+/*
+ * fixup_varattnos
+ *
+ * It fixes up varattno of Var node that referenced the relation with
+ * RLS policy, thus replaced to a sub-query. Here is no guarantee the
+ * varattno matches with TargetEntry's resno of the sub-query, so needs
+ * to adjust them.
+ */
+typedef struct {
+	Query  *parse;
+	Index	varno;
+	int		varlevelsup;
+} fixup_varattnos_context;
+
+static bool
+fixup_varattnos_walker(Node *node, fixup_varattnos_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var            *var = (Var *)node;
+		RangeTblEntry  *rte;
+
+		/*
+		 * Does this Var node reference the Query node currently we focused
+		 * on. If not, we simply ignore it.
+		 */
+		if (var->varlevelsup != context->varlevelsup ||
+			var->varno != context->varno)
+			return false;
+
+		rte = rt_fetch(var->varno, context->parse->rtable);
+		if (!rte)
+			elog(ERROR, "invalid varno %d", var->varno);
+
+		Assert(rte->rtekind == RTE_SUBQUERY);
+		Assert(rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+
+		if (list_length(rte->subquery->rtable) == 1)
+			var->varattno = lookup_pseudo_column(rte, var->varattno);
+		else
+			var->varattno = lookup_pseudo_column_multi(rte, var->varattno);
+
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool    result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varattnos_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varattnos_walker,
+								  (void *) context);
+}
+
+/*
+ * expand_range_table
+ *
+ * It replaces the given table reference by a simple sub-query.
+ */
+static void
+expand_range_table(RangeTblEntry *rte, RowMarkClause *rclause,
+				   Node *qual, int flags)
+{
+	Query		   *subqry;
+
+	subqry = make_pseudo_subquery(rte, rte->relid, qual, flags, rclause);
+
+	/* no permission checks are needed to subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+
+	rte->eref = makeAlias(get_rel_name(rte->relid), NIL);
+	rte->alias = makeAlias(get_rel_name(rte->relid), NIL);
+
+	rte->relid = InvalidOid;
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = subqry;
+	rte->security_barrier = (!qual ? false : true);
+	rte->inh = false;
+}
+
+/*
+ * expand_range_table_multi
+ *
+ * It replaces the given table references by an alternative sub-query that
+ * also contains multiple simple sub-queries.
+ */
+static void
+expand_range_table_multi(RangeTblEntry *rte, RowMarkClause *rclause,
+						 List *rls_relids, List *rls_quals, List *rls_flags)
+{
+	Query	   *baseqry;
+	SetOperationStmt *setop = NULL;
+	RangeTblEntry *subrte;
+	RangeTblRef	*subrtr;
+	ListCell   *cell1;
+	ListCell   *cell2;
+	ListCell   *cell3;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+	Assert(rte->relid == linitial_oid(rls_relids));
+	Assert(list_length(rls_relids) == list_length(rls_quals));
+	Assert(list_length(rls_relids) == list_length(rls_flags));
+	Assert(list_length(rls_relids) > 1);
+
+	/*
+	 * baseqry is the sub-query that should replaces reference to
+	 * the target relation, and will also have multiple sub-queries
+	 * to scan the relations with row-level security policy.
+	 */
+	baseqry = makeNode(Query);
+	baseqry->commandType = CMD_SELECT;
+	baseqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	forthree (cell1, rls_relids, cell2, rls_quals, cell3, rls_flags)
+	{
+		Oid		relid = lfirst_oid(cell1);
+		Node   *qual  = lfirst(cell2);
+		int		flags = lfirst_int(cell3);
+
+		/*
+		 * Construct a pseudo sub-query corresponding to the table
+		 * to be applied, then chain it with base-query.
+		 * If no security policy is configured, here is no reason
+		 * whey the sub-query should be security-barrier.
+		 */
+		subrte = makeNode(RangeTblEntry);
+		subrte->rtekind = RTE_SUBQUERY;
+		subrte->subquery
+			= make_pseudo_subquery(rte, relid, qual, flags, rclause);
+		subrte->security_barrier = (!qual ? false : true);
+		subrte->alias = makeAlias(get_rel_name(relid), NIL);
+		subrte->eref = makeAlias(get_rel_name(relid), NIL);
+
+		baseqry->rtable = lappend(baseqry->rtable, subrte);
+
+		/*
+		 * Also, we need to set up SetOperationStmt to connect pseudo
+		 * sub-queries according to simple UNION ALL.
+		 */
+		subrtr = makeNode(RangeTblRef);
+		subrtr->rtindex = list_length(baseqry->rtable);
+		if (!setop)
+		{
+			setop = makeNode(SetOperationStmt);
+			setop->op = SETOP_UNION;
+			setop->all = true;
+			setop->larg = (Node *)subrtr;
+			setop->rarg = NULL;
+		}
+		else if (!setop->rarg)
+		{
+			Assert(setop->larg != NULL);
+
+			setop->rarg = (Node *)subrtr;
+		}
+		else
+		{
+			SetOperationStmt   *setop_next;
+
+			Assert(setop->larg != NULL && setop->rarg != NULL);
+
+			setop_next = makeNode(SetOperationStmt);
+			setop_next->op = SETOP_UNION;
+			setop_next->all = true;
+			setop_next->larg = (Node *) setop;
+			setop_next->rarg = (Node *) subrtr;
+
+			setop = setop_next;
+		}
+	}
+	baseqry->setOperations = (Node *) setop;
+
+	/* no permission checks are needed to subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+	rte->eref = makeAlias(get_rel_name(rte->relid), NIL);
+	rte->alias = makeAlias(get_rel_name(rte->relid), NIL);
+
+	rte->relid = InvalidOid;
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = baseqry;
+	rte->security_barrier = false;
+}
+
+/*
+ * apply_relation_rowlevelsec
+ *
+ * This routine pulls row-level security policy of the target relaion
+ * given as range table entry.
+ */
+static bool
+apply_relation_rowlevelsec(Query *parse, RangeTblEntry *rte, Index rtindex,
+						   List *active_relids)
+{
+	Relation		rel;
+	LOCKMODE		lockmode;
+	List		   *relid_list = NIL;
+	List		   *quals_list = NIL;
+	List		   *flags_list = NIL;
+	ListCell	   *cell;
+	bool			result = false;
+
+	Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+	/*
+	 * XXX - Right now, we don't care about result relation of
+	 * UPDATE or DELETE statement. So, ignore it simply.
+	 */
+	if (parse->resultRelation == rtindex)
+		return false;
+
+	if (!rte->inh)
+	{
+		lockmode = NoLock;
+		relid_list = list_make1_oid(rte->relid);
+	}
+	else
+	{
+		/*
+		 * In case when the target relation may have inheritances,
+		 * we need to suggest an appropriate lock mode because it
+		 * is the first time to reference these tables in a series
+		 * of processes. For more details, see the comments in
+		 * expand_inherited_tables.
+		 * Also note that it does not guarantee the locks on child
+		 * tables being already acquired at expand_inherited_tables,
+		 * because row-level security routines can be bypassed if
+		 * RowLevelSecModeDisabled.
+		 */
+		if (rtindex == parse->resultRelation)
+			lockmode = RowExclusiveLock;
+		else
+		{
+			foreach (cell, parse->rowMarks)
+			{
+				Assert(IsA(lfirst(cell), RowMarkClause));
+				if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+					break;
+			}
+			if (cell)
+				lockmode = RowShareLock;
+			else
+				lockmode = AccessShareLock;
+		}
+		relid_list = find_all_inheritors(rte->relid, lockmode, NULL);
+	}
+
+	/*
+	 * Try to fetch row-level security policy of the target relation
+	 * or its children.
+	 */
+	foreach (cell, relid_list)
+	{
+		Expr	   *qual = NULL;
+		int			flags = 0;
+
+		rel = heap_open(lfirst_oid(cell), lockmode);
+
+		/*
+		 * Recursion check to prevent infinite recursion.
+		 */
+		if (list_member_oid(active_relids, RelationGetRelid(rel)))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							RelationGetRelationName(rel))));
+
+		/*
+		 * Pull out row-level security policy configured with built-in
+		 * features, if unprivileged users. Please note that superuser
+		 * can bypass it.
+		 */
+		if (rel->rlsdesc && !superuser())
+		{
+			RowLevelSecDesc	*rlsdesc = rel->rlsdesc;
+
+			qual = copyObject(rlsdesc->rlsqual);
+			if (rlsdesc->rlshassublinks)
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+		}
+
+		/*
+		 * Also, ask extensions whether they want to apply their own
+		 * row-level security policy. If both built-in and extension
+		 * has their own policy, it shall be merged.
+		 */
+		if (rowlevel_security_hook)
+		{
+			List   *temp;
+
+			temp = (*rowlevel_security_hook)(rel);
+			if (temp != NIL)
+			{
+				if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+					contain_subplans((Node *) temp))
+					flags |= RLS_FLAG_HAS_SUBLINKS;
+
+				if (qual != NULL)
+					temp = lappend(temp, qual);
+
+				if (list_length(temp) == 1)
+					qual = (Expr *)list_head(temp);
+				else
+					qual = makeBoolExpr(AND_EXPR, temp, -1);
+			}
+		}
+
+		/*
+		 * If either built-in or extension row-level security policy
+		 * is configured at least, we determine this table reference
+		 * should be replaced with an alternative sub-query.
+		 */
+		if (qual)
+		{
+			/* SubLink should be also extracted */
+			if (flags & RLS_FLAG_HAS_SUBLINKS)
+			{
+				fixup_sublink_context	context;
+
+				memset(&context, 0, sizeof(fixup_sublink_context));
+
+				context.exec_query_rewriter = true;
+				context.active_relids = lappend_oid(list_copy(active_relids),
+													RelationGetRelid(rel));
+				fixup_sublink_walker((Node *)qual, (void *) &context);
+
+				list_free(context.active_relids);
+			}
+			result = true;
+		}
+		quals_list = lappend(quals_list, qual);
+		flags_list = lappend_int(flags_list, flags);
+
+		heap_close(rel, NoLock);  /* close the relation, but keep locks */
+	}
+
+	if (result)
+	{
+		RowMarkClause *rclause = NULL;
+
+		/*
+		 * Push-down row-level lock of the target relation, since sub-query
+		 * does not support FOR SHARE/FOR UPDATE locks being assigned.
+		 */
+		foreach (cell, parse->rowMarks)
+		{
+			if (((RowMarkClause *) lfirst(cell))->rti == rtindex)
+			{
+				rclause = lfirst(cell);
+				parse->rowMarks = list_delete(parse->rowMarks, rclause);
+				break;
+			}
+		}
+
+		/*
+		 * If no children, a range table entry of relation is replaced
+		 * with a simple sub-query. On the other hand, it is replaced
+		 * with a sub-query that also contains simple but multiple sub-
+		 * queries being connected with UNION ALL.
+		 */
+		if (list_length(relid_list) == 1)
+			expand_range_table(rte, rclause,
+							   linitial(quals_list),
+							   linitial_int(flags_list));
+		else
+			expand_range_table_multi(rte, rclause,
+									 relid_list, quals_list, flags_list);
+	}
+	list_free(relid_list);
+	list_free(quals_list);
+	list_free(flags_list);
+
+	return result;
+}
+
+/*
+ * apply_subquery_rowlevelsec
+ *
+ * This routine applies row-level security policy of the table being
+ * referenced in the given sub- or toplevel- query recursively.
+ */
+static bool
+apply_subquery_rowlevelsec(Query *parse, List *active_relids)
+{
+	Index		rtindex = 0;
+	bool		has_rowlevel_sec = false;
+	ListCell   *cell;
+
+	foreach (cell, parse->rtable)
+	{
+		RangeTblEntry  *rte = lfirst(cell);
+		rtindex++;
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			if (apply_relation_rowlevelsec(parse, rte, rtindex,
+										   active_relids))
+			{
+				/*
+				 * If the referenced relation was replaced with
+				 * a sub-query, varattno of Var node needs to be
+				 * adjusted according to the target-list of the
+				 * pseudo sub-query.
+				 */
+				fixup_varattnos_context	context;
+
+				context.parse = parse;
+				context.varno = rtindex;
+				context.varlevelsup = 0;
+
+				query_tree_walker(parse, fixup_varattnos_walker,
+								  (void *) &context, 0);
+
+				has_rowlevel_sec = true;
+			}
+		}
+		else if (rte->rtekind == RTE_SUBQUERY)
+		{
+			if (apply_subquery_rowlevelsec(rte->subquery, active_relids))
+				has_rowlevel_sec = true;
+		}
+	}
+
+	/*
+	 * If the given parse tree contains SubLink node, we need to walk
+	 * into the parse tree to extract them in addition to the range-
+	 * table entries.
+	 * Query->hasSubLinks gives a hint for optimization to skip it.
+	 */
+	if (parse->hasSubLinks)
+	{
+		fixup_sublink_context	context;
+
+		memset(&context, 0, sizeof(fixup_sublink_context));
+		context.active_relids = active_relids;
+
+		query_tree_walker(parse, fixup_sublink_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RANGE_TABLE);
+		if (context.has_rowlevel_sec)
+			has_rowlevel_sec = true;
+	}
+	return has_rowlevel_sec;
+}
+
+/*
+ * applyRowLevelSecurity
+ *
+ * It applies row-level security policy of the relation, if a table has
+ * its security policy, reference to the table shall be replaced by sub-
+ * query that simply scan the table being replaced with security policy
+ * as qualifiers. Of course, security-barrier flag is also added to the
+ * sub-query to prevent unintentional push-down of malicious functions
+ * being executed prior to evaluation of security policy.
+ * In the result, all user can see is rows of the relation that satisfies
+ * the qualifier supplied as security policy.
+ *
+ * One complex stuff is table reference with inherited child tables.
+ * In case when the give parse tree referenced a table with child tables,
+ * and one of them has row-level security, reference to the parent table
+ * also shall be replaced by a sub-query that contains multiple queries
+ * connected with UNION ALL.
+ *
+ * For example, if table t1 has t2 and t3 as children, security policy
+ * of t1 is (x > 10) and t3 is (x % 2 = 0), the following query
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * should be rewritten as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM only t1 WHERE x > 10
+ *     UNION ALL
+ *     SELECT x, y FROM only t2
+ *     UNION ALL
+ *     SELECT x, y FROM only t3 WHERE x % 2 = 0
+ *   ) t1 WHERE f_leak(y);
+ * would be given. It is identical with how table inheritance performs on.
+ *
+ * Row-level security policy can contain SubLink, thus, it allows to refer
+ * another relation to make access control decision. Even if it contains
+ * view, it shall be extracted to the normal form. In addition, row-level
+ * security policy of underlying tables are also applied recursively.
+ */
+void
+applyRowLevelSecurity(PlannerGlobal *glob, Query *parse)
+{
+	/* mode checks */
+	if (rowlevel_security_mode == RowLevelSecModeDisabled)
+		return;
+
+	/*
+	 * Extract row-level security policy of the underlying tables
+	 * recursively, and replace the references by sub-query with
+	 * security-barrier and qualifiers to filter out rows.
+	 * Plan has to be invalidated if different user-id was applied
+	 * on executor stage.
+	 */
+	if (apply_subquery_rowlevelsec(parse, NIL))
+	{
+		PlanInvalItem  *pi_item;
+
+		glob->planUserId = GetUserId();
+
+		pi_item = makeNode(PlanInvalItem);
+		pi_item->cacheId = AUTHOID;
+		pi_item->hashValue =
+			GetSysCacheHashValue1(AUTHOID,
+								  ObjectIdGetDatum(glob->planUserId));
+		glob->invalItems = lappend(glob->invalItems, pi_item);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e4ff76e..a3d445d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2127,6 +2127,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b75b2d9..9494889 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8f75948..f08fcd8 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2245,3 +2245,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..19a2255 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -43,6 +43,7 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "miscadmin.h"
+#include "optimizer/rowlevelsec.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
@@ -2999,6 +3000,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	RowLevelSecMode	save_rls_mode;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3081,6 +3083,15 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
 						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
 
+	/*
+	 * Disabled row-level security in case when foreign-key relation is
+	 * queried to check existence of tupls that references the tuple to
+	 * be modified on the primary-key side.
+	 */
+	save_rls_mode = getRowLevelSecurityMode();
+	if (source_is_pk)
+		setRowLevelSecurityMode(RowLevelSecModeDisabled);
+
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
 									  vals, nulls,
@@ -3090,6 +3101,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	/* Restore UID and security context */
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 
+	/* Restore row-level security performing mode */
+	setRowLevelSecurityMode(save_rls_mode);
+
 	/* Check result */
 	if (spi_result < 0)
 		elog(ERROR, "SPI_execute_snapshot returned %d", spi_result);
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8c9ebe0..28ba831 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4162,6 +4176,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dd2019a..e18d2f4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3867,6 +3867,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3891,7 +3892,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3911,7 +3950,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3947,7 +3987,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3982,7 +4023,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4017,7 +4059,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4053,7 +4096,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4088,7 +4132,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4119,7 +4164,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4145,7 +4191,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4181,7 +4228,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4229,6 +4277,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4271,6 +4320,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12803,6 +12856,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2aa2060..5485101 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -256,6 +256,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..5a64d1b
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,60 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	ResetRowLevelSecurity(Relation relation);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 09b15e7..4118794 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -1232,6 +1233,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 2b2742d..f20923a 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..a09e15c
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,31 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+typedef enum {
+	RowLevelSecModeEnabled,
+	RowLevelSecModeDisabled,
+} RowLevelSecMode;
+
+extern RowLevelSecMode getRowLevelSecurityMode(void);
+extern void setRowLevelSecurityMode(RowLevelSecMode new_mode);
+
+extern void	applyRowLevelSecurity(PlannerGlobal *root, Query *parse);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index e3bb35f..d13ef32 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 50625d4..d470cad 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 extern Node *build_column_default(Relation rel, int attrno);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..46921cf
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,853 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+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;
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+(16 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-C-A)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is unclassified, category(-CBA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-C-A)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(-CBA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-C-A)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(-CBA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  60 |      0 | 0101      | this document is unclassified, category(-C-A)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  80 |      0 | 0111      | this document is unclassified, category(-CBA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 140 |      1 | 0101      | this document is classified, category(-C-A)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 160 |      1 | 0111      | this document is classified, category(-CBA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 220 |      2 | 0101      | this document is secret, category(-C-A)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+ 240 |      2 | 0111      | this document is secret, category(-CBA)
+(24 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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 account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Hash Join  (cost=30.34..57.95 rows=2 width=117)
+   Hash Cond: (document.did = browse.did)
+   ->  Seq Scan on document  (cost=8.27..31.15 rows=343 width=49)
+         Filter: (dlevel <= $0)
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=4)
+                 Index Cond: (pguser = "current_user"())
+   ->  Hash  (cost=22.06..22.06 rows=1 width=72)
+         ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+               Filter: f_leak((browse.browse)::text)
+               ->  Seq Scan on browse browse_1  (cost=0.00..22.00 rows=4 width=168)
+                     Filter: (pguser = "current_user"())
+(12 rows)
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ERROR:  must be owner of relation document
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(---A)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(--BA)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(---A)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(--BA)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(---A)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(--BA)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  20 |      0 | 0001      | this document is unclassified, category(---A)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  40 |      0 | 0011      | this document is unclassified, category(--BA)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 100 |      1 | 0001      | this document is classified, category(---A)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 120 |      1 | 0011      | this document is classified, category(--BA)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 180 |      2 | 0001      | this document is secret, category(---A)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 200 |      2 | 0011      | this document is secret, category(--BA)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user1,20,07-01-2012)
+NOTICE:  f_leak => (rls_regress_user1,40,07-02-2012)
+NOTICE:  f_leak => (rls_regress_user1,110,07-03-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+(3 rows)
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+NOTICE:  f_leak => this document is unclassified, category(----)
+NOTICE:  f_leak => this document is unclassified, category(--B-)
+NOTICE:  f_leak => this document is unclassified, category(-C--)
+NOTICE:  f_leak => this document is unclassified, category(-CB-)
+NOTICE:  f_leak => this document is classified, category(----)
+NOTICE:  f_leak => this document is classified, category(--B-)
+NOTICE:  f_leak => this document is classified, category(-C--)
+NOTICE:  f_leak => this document is classified, category(-CB-)
+NOTICE:  f_leak => this document is secret, category(----)
+NOTICE:  f_leak => this document is secret, category(--B-)
+NOTICE:  f_leak => this document is secret, category(-C--)
+NOTICE:  f_leak => this document is secret, category(-CB-)
+ did | dlevel | dcategory |                    dtitle                     
+-----+--------+-----------+-----------------------------------------------
+  10 |      0 | 0000      | this document is unclassified, category(----)
+  30 |      0 | 0010      | this document is unclassified, category(--B-)
+  50 |      0 | 0100      | this document is unclassified, category(-C--)
+  70 |      0 | 0110      | this document is unclassified, category(-CB-)
+  90 |      1 | 0000      | this document is classified, category(----)
+ 110 |      1 | 0010      | this document is classified, category(--B-)
+ 130 |      1 | 0100      | this document is classified, category(-C--)
+ 150 |      1 | 0110      | this document is classified, category(-CB-)
+ 170 |      2 | 0000      | this document is secret, category(----)
+ 190 |      2 | 0010      | this document is secret, category(--B-)
+ 210 |      2 | 0100      | this document is secret, category(-C--)
+ 230 |      2 | 0110      | this document is secret, category(-CB-)
+(12 rows)
+
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+NOTICE:  f_leak => (rls_regress_user2,30,07-04-2012)
+NOTICE:  f_leak => (rls_regress_user2,50,07-05-2012)
+NOTICE:  f_leak => (rls_regress_user2,90,07-06-2012)
+NOTICE:  f_leak => (rls_regress_user2,130,07-07-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,150,07-08-2012)
+NOTICE:  f_leak => (rls_regress_user2,190,07-09-2012)
+NOTICE:  f_leak => (rls_regress_user2,210,07-10-2012)
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  30 |      0 | 0010      | this document is unclassified, category(--B-) | rls_regress_user2 | 07-04-2012
+  50 |      0 | 0100      | this document is unclassified, category(-C--) | rls_regress_user2 | 07-05-2012
+  90 |      1 | 0000      | this document is classified, category(----)   | rls_regress_user2 | 07-06-2012
+ 130 |      1 | 0100      | this document is classified, category(-C--)   | rls_regress_user2 | 07-07-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 150 |      1 | 0110      | this document is classified, category(-CB-)   | rls_regress_user2 | 07-08-2012
+ 190 |      2 | 0010      | this document is secret, category(--B-)       | rls_regress_user2 | 07-09-2012
+ 210 |      2 | 0100      | this document is secret, category(-C--)       | rls_regress_user2 | 07-10-2012
+(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: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account
+                 Index Cond: (pguser = "current_user"())
+(7 rows)
+
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+                                        QUERY PLAN                                        
+------------------------------------------------------------------------------------------
+ Nested Loop  (cost=8.27..55.90 rows=1 width=117)
+   Join Filter: (document.did = browse.did)
+   ->  Subquery Scan on browse  (cost=0.00..22.06 rows=1 width=72)
+         Filter: f_leak((browse.browse)::text)
+         ->  Seq Scan on browse browse_1  (cost=0.00..22.00 rows=4 width=168)
+               Filter: (pguser = "current_user"())
+   ->  Seq Scan on document  (cost=8.27..33.72 rows=5 width=49)
+         Filter: ((dcategory & $0) = B'0000'::"bit")
+         InitPlan 1 (returns $0)
+           ->  Index Scan using account_pkey on account  (cost=0.00..8.27 rows=1 width=9)
+                 Index Cond: (pguser = "current_user"())
+(11 rows)
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+ did | dlevel | dcategory |                    dtitle                     |      pguser       |    ymd     
+-----+--------+-----------+-----------------------------------------------+-------------------+------------
+  10 |      0 | 0000      | this document is unclassified, category(----) |                   | 
+  20 |      0 | 0001      | this document is unclassified, category(---A) | rls_regress_user1 | 07-01-2012
+  30 |      0 | 0010      | this document is unclassified, category(--B-) |                   | 
+  40 |      0 | 0011      | this document is unclassified, category(--BA) | rls_regress_user1 | 07-02-2012
+  90 |      1 | 0000      | this document is classified, category(----)   |                   | 
+ 100 |      1 | 0001      | this document is classified, category(---A)   |                   | 
+ 110 |      1 | 0010      | this document is classified, category(--B-)   | rls_regress_user1 | 07-03-2012
+ 120 |      1 | 0011      | this document is classified, category(--BA)   |                   | 
+ 170 |      2 | 0000      | this document is secret, category(----)       |                   | 
+ 180 |      2 | 0001      | this document is secret, category(---A)       |                   | 
+ 190 |      2 | 0010      | this document is secret, category(--B-)       |                   | 
+ 200 |      2 | 0011      | this document is secret, category(--BA)       |                   | 
+(12 rows)
+
+DELETE FROM document WHERE did = 30;			-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(30) is still referenced from table "browse".
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+ERROR:  update or delete on table "document" violates foreign key constraint "browse_did_fkey" on table "browse"
+DETAIL:  Key (did)=(90) is still referenced from table "browse".
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+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);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(7 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  LockRows
+                     ->  Seq Scan on t1 t1_1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  LockRows
+                     ->  Seq Scan on t2 t2_1
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3 t3_1
+(13 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                   QUERY PLAN                    
+-------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  LockRows
+                     ->  Seq Scan on t1 t1_1
+                           Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               Filter: f_leak(t2.b)
+               ->  LockRows
+                     ->  Seq Scan on t2 t2_1
+                           Filter: ((a % 2) = 1)
+         ->  Subquery Scan on t3
+               ->  LockRows
+                     ->  Seq Scan on t3 t3_1
+                           Filter: f_leak(b)
+(16 rows)
+
+--
+-- 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%';
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW LEVEL SECURITY (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW LEVEL SECURITY
+   (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 for relation "s1"
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+-- prepared statement with rls_regress_user0 privilege
+PREPARE p1(int) AS SELECT * FROM t1 WHERE a <= $1;
+EXECUTE p1(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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            
+---------------------------------
+ Result
+   ->  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)
+(8 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           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2
+               Filter: (a <= 2)
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 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           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2
+               Filter: (a = 2)
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Seq Scan on t2
+               Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 other objects
+DETAIL:  drop cascades to table account
+drop cascades to function f_leak(text)
+drop cascades to table document
+drop cascades to table browse
+drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
+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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 663bf8a..4c4552f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index be789e3..eea4c3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..21def6e
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,273 @@
+--
+-- 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 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 SCHEMA rls_regress_schema;
+GRANT ALL ON SCHEMA rls_regress_schema TO public;
+SET search_path = rls_regress_schema;
+
+CREATE TABLE account (
+	   pguser       name primary key,
+	   slevel		int,
+	   scategory	bit(4)
+);
+GRANT SELECT,REFERENCES ON TABLE account TO public;
+INSERT INTO account VALUES
+  ('rls_regress_user1', 1, B'0011'),
+  ('rls_regress_user2', 2, B'0110'),
+  ('rls_regress_user3', 0, B'0101');
+
+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;
+
+-- Creation of Test Data
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+CREATE TABLE document (
+       did           int primary key,
+       dlevel        int,
+       dcategory     bit(4),
+	   dtitle        text
+);
+GRANT ALL ON document TO public;
+INSERT INTO document VALUES
+  ( 10, 0, B'0000', 'this document is unclassified, category(----)'),
+  ( 20, 0, B'0001', 'this document is unclassified, category(---A)'),
+  ( 30, 0, B'0010', 'this document is unclassified, category(--B-)'),
+  ( 40, 0, B'0011', 'this document is unclassified, category(--BA)'),
+  ( 50, 0, B'0100', 'this document is unclassified, category(-C--)'),
+  ( 60, 0, B'0101', 'this document is unclassified, category(-C-A)'),
+  ( 70, 0, B'0110', 'this document is unclassified, category(-CB-)'),
+  ( 80, 0, B'0111', 'this document is unclassified, category(-CBA)'),
+  ( 90, 1, B'0000', 'this document is classified, category(----)'),
+  (100, 1, B'0001', 'this document is classified, category(---A)'),
+  (110, 1, B'0010', 'this document is classified, category(--B-)'),
+  (120, 1, B'0011', 'this document is classified, category(--BA)'),
+  (130, 1, B'0100', 'this document is classified, category(-C--)'),
+  (140, 1, B'0101', 'this document is classified, category(-C-A)'),
+  (150, 1, B'0110', 'this document is classified, category(-CB-)'),
+  (160, 1, B'0111', 'this document is classified, category(-CBA)'),
+  (170, 2, B'0000', 'this document is secret, category(----)'),
+  (180, 2, B'0001', 'this document is secret, category(---A)'),
+  (190, 2, B'0010', 'this document is secret, category(--B-)'),
+  (200, 2, B'0011', 'this document is secret, category(--BA)'),
+  (210, 2, B'0100', 'this document is secret, category(-C--)'),
+  (220, 2, B'0101', 'this document is secret, category(-C-A)'),
+  (230, 2, B'0110', 'this document is secret, category(-CB-)'),
+  (240, 2, B'0111', 'this document is secret, category(-CBA)');
+
+CREATE TABLE browse (
+  pguser       name references account(pguser),
+  did          int  references document(did),
+  ymd          date
+);
+GRANT ALL ON browse TO public;
+INSERT INTO browse VALUES
+  ('rls_regress_user1',  20, '2012-07-01'),
+  ('rls_regress_user1',  40, '2012-07-02'),
+  ('rls_regress_user1', 110, '2012-07-03'),
+  ('rls_regress_user2',  30, '2012-07-04'),
+  ('rls_regress_user2',  50, '2012-07-05'),
+  ('rls_regress_user2',  90, '2012-07-06'),
+  ('rls_regress_user2', 130, '2012-07-07'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 150, '2012-07-08'),
+  ('rls_regress_user2', 190, '2012-07-09'),
+  ('rls_regress_user2', 210, '2012-07-10'),
+  ('rls_regress_user3',  10, '2012-07-11'),
+  ('rls_regress_user3',  50, '2012-07-12');
+
+-- user's sensitivity level must higher than document's level
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dlevel <= (SELECT slevel FROM account WHERE pguser = current_user));
+ALTER TABLE browse SET ROW LEVEL SECURITY
+  (pguser = current_user);
+
+-- viewpoint from rls_regress_user1
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- change row-level security policy
+ALTER TABLE document RESET ROW LEVEL SECURITY;      -- failed
+ALTER TABLE document SET ROW LEVEL SECURITY (true); -- failed
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+-- switch to category based control from level based
+ALTER TABLE document SET ROW LEVEL SECURITY
+  (dcategory & (SELECT ~scategory FROM account WHERE pguser = current_user) = B'0000');
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+EXPLAIN (costs off) SELECT * FROM document WHERE f_leak(dtitle);
+EXPLAIN SELECT * FROM document NATURAL JOIN browse WHERE f_leak(browse::text);
+
+-- Failed to update PK row referenced by invisible FK
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document NATURAL LEFT JOIN browse;
+DELETE FROM document WHERE did = 30;			-- failed
+UPDATE document SET did = 9999 WHERE did = 90;	-- failed
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (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 * 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;
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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 TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%d2%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion via view)
+
+-- Now COPY TO command does not support RLS
+-- COPY t1 TO stdin;
+
+-- 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;
+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;
+EXECUTE p2(2);
+EXPLAIN (costs off) EXECUTE p2(2);
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-10-18 18:19                                           ` Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-19 09:00                                             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Alvaro Herrera @ 2012-10-18 18:19 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Dean Rasheed <dean.a.rasheed@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

Kohei KaiGai escribió:
> The revised patch fixes the problem that Daen pointed out.

Robert, would you be able to give this latest version of the patch a
look?

(KaiGai, does it still apply cleanly? If not, please submit a rebased
version.)

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
@ 2012-10-19 09:00                                             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-19 10:02                                               ` Re: [v9.3] Row-Level Security Erik Rijkers <er@xs4all.nl>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-10-19 09:00 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Dean Rasheed <dean.a.rasheed@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/10/18 Alvaro Herrera <alvherre@2ndquadrant.com>:
> Kohei KaiGai escribió:
>> The revised patch fixes the problem that Daen pointed out.
>
> Robert, would you be able to give this latest version of the patch a
> look?
>
> (KaiGai, does it still apply cleanly? If not, please submit a rebased
> version.)
>
I confirmed I could apply the latest patch cleanly.

-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-19 09:00                                             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-10-19 10:02                                               ` Erik Rijkers <er@xs4all.nl>
  0 siblings, 0 replies; 165+ messages in thread

From: Erik Rijkers @ 2012-10-19 10:02 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Fri, October 19, 2012 11:00, Kohei KaiGai wrote:
> I confirmed I could apply the latest patch cleanly.
>

FWIW, I spent a few sessions (amounting to a few hours) trying to break, or get past SET ROW LEVEL
SECURITY and have not yet succeeded.  So far so good.

(I haven't looked at code)

Erik Rijkers





^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
@ 2012-10-22 13:33                                             ` Robert Haas <robertmhaas@gmail.com>
  2012-10-22 16:17                                               ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 2 replies; 165+ messages in thread

From: Robert Haas @ 2012-10-22 13:33 UTC (permalink / raw)
  To: Alvaro Herrera <alvherre@2ndquadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Thu, Oct 18, 2012 at 2:19 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
> Kohei KaiGai escribió:
>> The revised patch fixes the problem that Daen pointed out.
>
> Robert, would you be able to give this latest version of the patch a
> look?

Yeah, sorry I've been completely sidelined this CommitFest. It's been
a crazy couple of months.  Prognosis for future craziness reduction
uncertain.  Comments:

The documentation lists several documented limitations that I would
like to analyze a little bit.  First, it says that row-level security
policies are not applied on UPDATE or DELETE.  That sounds downright
dangerous to me.  Is there some really compelling reason we're not
doing it?  Second, it says that row-level security policies are not
currently applied on INSERT, so you should use a trigger, but implies
that this will change in the future.  I don't think we should change
that in the future; I think relying on triggers for that case is just
fine.  Note that it could be an issue with the post-image for UPDATES,
as well, and I think the trigger solution is similarly adequate to
cover that case.  With respect to the documented limitation regarding
DECLARE/FETCH, what exactly will happen?  Can we describe this a bit
more clearly rather than just saying the behavior will be
unpredictable?

It looks suspiciously as if the row-level security mode needs to be
saved and restored in all the same places we call save and restore the
user ID and security context.  Is there some reason the
row-level-security-enabled flag shouldn't just become another bit in
the security context?  Then we'd get all of this save/restore logic
mostly for free.

ATExecSetRowLevelSecurity() calls SetRowLevelSecurity() or
ResetRowLevelSecurity() to update pg_rowlevelsec, but does the
pg_class update itself.  I think that all of this logic should be
moved into  a single function, or at least functions in the same file,
with the one that only updates pg_rowlevelsec being static and
therefore not able to be called from outside the file.  We always need
the pg_class update and the pg_rowlevelsec update to happen together,
so it's not good to have an exposed function that does one of those
updates but not the other.  I think the simplest thing is just to move
ATExecSetRowLevelSecurity to pg_rowlevelsec.c and rename it to
SetRowLevelSecurity() and then give it two static helper functions,
say InsertPolicyRow() and DeletePolicyRow().

I think it would be good if Tom could review the query-rewriting parts
of this (viz rowlevelsec.c) as I am not terribly familiar with this
machinery, and of course anything we get wrong here will have security
consequences.  At first blush, I'm somewhat concerned about the fact
that we're trying to do this after query rewriting; that seems like it
could break things.  I know KaiGai mentioned upthread that the
rewriter won't be rerun if the plan is invalidated, but (1) why is
that OK now? and (2) if it is OK now, then why is it OK to do
rewriting of the RLS qual - only - after rewriting if all of the rest
of the rewriting needs to happen earlier?

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2012-10-22 16:17                                               ` Tom Lane <tgl@sss.pgh.pa.us>
  2012-10-22 17:44                                                 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Tom Lane @ 2012-10-22 16:17 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

Robert Haas <robertmhaas@gmail.com> writes:
> The documentation lists several documented limitations that I would
> like to analyze a little bit.  First, it says that row-level security
> policies are not applied on UPDATE or DELETE.  That sounds downright
> dangerous to me.  Is there some really compelling reason we're not
> doing it?

[ blink... ]  Isn't that a security hole big enough for a Mack truck?

	UPDATE tab SET foo = foo RETURNING *;

sucks out all the data just fine, if RLS doesn't apply to it.

Having said that, I fear that sensible row-level security for updates is
at least one order of magnitude harder than sensible row-level security
for selects.  We've speculated about how to define that in the past,
IIRC, but without any very satisfactory outcome.

			regards, tom lane




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 16:17                                               ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
@ 2012-10-22 17:44                                                 ` Robert Haas <robertmhaas@gmail.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Robert Haas @ 2012-10-22 17:44 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Mon, Oct 22, 2012 at 12:17 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> The documentation lists several documented limitations that I would
>> like to analyze a little bit.  First, it says that row-level security
>> policies are not applied on UPDATE or DELETE.  That sounds downright
>> dangerous to me.  Is there some really compelling reason we're not
>> doing it?
>
> [ blink... ]  Isn't that a security hole big enough for a Mack truck?
>
>         UPDATE tab SET foo = foo RETURNING *;
>
> sucks out all the data just fine, if RLS doesn't apply to it.

Yep.

> Having said that, I fear that sensible row-level security for updates is
> at least one order of magnitude harder than sensible row-level security
> for selects.  We've speculated about how to define that in the past,
> IIRC, but without any very satisfactory outcome.

Uh, I don't agree.  SELECT and DELETE are pretty much identical cases.
 UPDATE needs all the same stuff that those two cases need, plus it
has an additional problem that it shares with INSERT - namely, someone
might insert a tuple that they cannot see or update a tuple such that
they can no longer see it.  However, both of those problems can be
handled via triggers, for now and maybe forever.  In contrast, the
problem that SELECT has - which UPDATE and DELETE also share - namely,
of rows being visible that should not be - is not nearly so
susceptible to that approach, both for performance reasons and because
there's no such thing as a trigger on SELECT.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
@ 2012-10-22 20:54                                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-10-22 20:54 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/10/22 Robert Haas <robertmhaas@gmail.com>:
> On Thu, Oct 18, 2012 at 2:19 PM, Alvaro Herrera
> <alvherre@2ndquadrant.com> wrote:
>> Kohei KaiGai escribió:
>>> The revised patch fixes the problem that Daen pointed out.
>>
>> Robert, would you be able to give this latest version of the patch a
>> look?
>
> Yeah, sorry I've been completely sidelined this CommitFest. It's been
> a crazy couple of months.  Prognosis for future craziness reduction
> uncertain.  Comments:
>
> The documentation lists several documented limitations that I would
> like to analyze a little bit.  First, it says that row-level security
> policies are not applied on UPDATE or DELETE.  That sounds downright
> dangerous to me.  Is there some really compelling reason we're not
> doing it?

It intends to simplify the patch to avoid doing everything within a single
patch. I'll submit the patch supporting UPDATE and DELETE for CF-Nov
in addition to the base one.

>  Second, it says that row-level security policies are not
> currently applied on INSERT, so you should use a trigger, but implies
> that this will change in the future.  I don't think we should change
> that in the future; I think relying on triggers for that case is just
> fine.  Note that it could be an issue with the post-image for UPDATES,
> as well, and I think the trigger solution is similarly adequate to
> cover that case.

Hmm. I should not have written this in section of the current limitation.
It may give impression the behavior will be changed future.
OK, I'll try to revise the documentation stuff.

>  With respect to the documented limitation regarding
> DECLARE/FETCH, what exactly will happen?  Can we describe this a bit
> more clearly rather than just saying the behavior will be
> unpredictable?
>
In case when user-id was switched after declaration of a cursor that
contains qualifier depending on current_user, its results set contains
rows with old user-id and rows with new user-id.

Here is one other option rather than documentation fix.
As we had a discussion on the upthread, it can be solved if we restore
the user-id associated with the portal to be run, however, a problem is
some commands switches user-id inside of the portal.
http://archives.postgresql.org/pgsql-hackers/2012-07/msg00055.php

Is there some good idea to avoid the problem?

> It looks suspiciously as if the row-level security mode needs to be
> saved and restored in all the same places we call save and restore the
> user ID and security context.  Is there some reason the
> row-level-security-enabled flag shouldn't just become another bit in
> the security context?  Then we'd get all of this save/restore logic
> mostly for free.
>
It seems to me a good idea, but I didn't find out this.

> ATExecSetRowLevelSecurity() calls SetRowLevelSecurity() or
> ResetRowLevelSecurity() to update pg_rowlevelsec, but does the
> pg_class update itself.  I think that all of this logic should be
> moved into  a single function, or at least functions in the same file,
> with the one that only updates pg_rowlevelsec being static and
> therefore not able to be called from outside the file.  We always need
> the pg_class update and the pg_rowlevelsec update to happen together,
> so it's not good to have an exposed function that does one of those
> updates but not the other.  I think the simplest thing is just to move
> ATExecSetRowLevelSecurity to pg_rowlevelsec.c and rename it to
> SetRowLevelSecurity() and then give it two static helper functions,
> say InsertPolicyRow() and DeletePolicyRow().
>
OK, I'll rework the code.

> I think it would be good if Tom could review the query-rewriting parts
> of this (viz rowlevelsec.c) as I am not terribly familiar with this
> machinery, and of course anything we get wrong here will have security
> consequences.  At first blush, I'm somewhat concerned about the fact
> that we're trying to do this after query rewriting; that seems like it
> could break things.  I know KaiGai mentioned upthread that the
> rewriter won't be rerun if the plan is invalidated, but (1) why is
> that OK now? and (2) if it is OK now, then why is it OK to do
> rewriting of the RLS qual - only - after rewriting if all of the rest
> of the rewriting needs to happen earlier?
>
I just follow the existing behavior of plan invalidation; that does not
re-run the query rewriter. So, if we have no particular reason why
we should not run the rewriter again to handle RLS quals, it might
be an option to handle RLS as a part of rewriter.

At least, here is two problems. 1) System column is problematic
when SELECT statement is replaced by sub-query. 2) It makes
infinite recursion when a certain table has SELECT INSTEAD
rule with a sub-query on the same table.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>




^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-11-15 21:07                                                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 17:02                                                   ` Re: [v9.3] Row-Level Security Simon Riggs <simon@2ndQuadrant.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Kohei KaiGai @ 2012-11-15 21:07 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

The attached patch is a revised version of row-level security
feature.
According to Robert's suggestion, I reworked implementation
around ALTER command, and logic to disable RLS during
FK/PK constraint checks.

In addition, I moved the entrypoint to apply row-level security
policy on the query tree next to the expand_inherited_tables,
because it became clear my previous approach is not
a straight-forward way to support update / delete cases.

This patch performs to replace RangeTblEntry of tables with
RLS policy by sub-queries that simply references the original
table with configured RLS policy. Also, the sub-queries have
security_barrier flag to prevent non-leakproof functions being
pushed down from outside of the sub-query.

This sub-query has target-list that just references columns of
underlying table, and ordered according to column definition
of the original table. So, we don't need to adjust varattno of
Var-node that reference regular columns, even though the
RangeTblEntry was replaced.
On the other hand, system-column is problematic because
sub-query does not have these columns due to nature of them.
So, I inject a logic to adjust varattno of Var-node that references
system-column of the target tables being replaced.
It works fine as follows:

postgres=> ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);
ALTER TABLE
postgres=> ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);
ALTER TABLE
postgres=> EXPLAIN (costs off) SELECT tableoid, * FROM t1 WHERE b like '%';
                QUERY PLAN
-------------------------------------------
 Result
   ->  Append
         ->  Subquery Scan on t1
               Filter: (t1.b ~~ '%'::text)
               ->  Seq Scan on t1 t1_1
                     Filter: ((a % 2) = 0)
         ->  Subquery Scan on t2
               Filter: (t2.b ~~ '%'::text)
               ->  Seq Scan on t2 t2_1
                     Filter: ((a % 2) = 1)
         ->  Seq Scan on t3
               Filter: (b ~~ '%'::text)
(12 rows)

postgres=> SELECT tableoid, * FROM t1 WHERE b like '%';
 tableoid | a  |  b
----------+----+-----
    16385 |  2 | bbb
    16385 |  4 | ddd
    16385 |  6 | fff
    16391 | 11 | sss
    16391 | 13 | uuu
    16391 | 15 | yyy
    16397 | 21 | xyz
    16397 | 22 | yzx
    16397 | 23 | zxy
(9 rows)

Also, UPDATE / DELETE statement

postgres=> EXPLAIN (costs off) UPDATE t1 SET b = b || '_updt' WHERE b like '%';
             QUERY PLAN
-------------------------------------
 Update on t1
   ->  Subquery Scan on t1
         Filter: (t1.b ~~ '%'::text)
         ->  Seq Scan on t1 t1_1
               Filter: ((a % 2) = 0)
   ->  Subquery Scan on t2
         Filter: (t2.b ~~ '%'::text)
         ->  Seq Scan on t2 t2_1
               Filter: ((a % 2) = 1)
   ->  Seq Scan on t3
         Filter: (b ~~ '%'::text)
(11 rows)

postgres=> UPDATE t1 SET b = b || '_updt' WHERE b like '%';
UPDATE 9

However, UPDATE / DELETE support is not perfect right now.
In case when we try to update / delete a table with inherited
children and RETURNING clause was added, is loses right
references to the pseudo columns, even though it works fine
without inherited children.

postgres=> UPDATE only t1 SET b = b || '_updt' WHERE b like '%' RETURNING *;
 a |    b
---+----------
 2 | bbb_updt
 4 | ddd_updt
 6 | fff_updt
(3 rows)

UPDATE 3
postgres=> UPDATE t1 SET b = b || '_updt' WHERE b like '%' RETURNING *;
ERROR:  variable not found in subplan target lists

I'm still under investigation of this behavior. Any comments
will be helpful to solve this problem.

Thanks,

2012/10/22 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> 2012/10/22 Robert Haas <robertmhaas@gmail.com>:
>> On Thu, Oct 18, 2012 at 2:19 PM, Alvaro Herrera
>> <alvherre@2ndquadrant.com> wrote:
>>> Kohei KaiGai escribió:
>>>> The revised patch fixes the problem that Daen pointed out.
>>>
>>> Robert, would you be able to give this latest version of the patch a
>>> look?
>>
>> Yeah, sorry I've been completely sidelined this CommitFest. It's been
>> a crazy couple of months.  Prognosis for future craziness reduction
>> uncertain.  Comments:
>>
>> The documentation lists several documented limitations that I would
>> like to analyze a little bit.  First, it says that row-level security
>> policies are not applied on UPDATE or DELETE.  That sounds downright
>> dangerous to me.  Is there some really compelling reason we're not
>> doing it?
>
> It intends to simplify the patch to avoid doing everything within a single
> patch. I'll submit the patch supporting UPDATE and DELETE for CF-Nov
> in addition to the base one.
>
>>  Second, it says that row-level security policies are not
>> currently applied on INSERT, so you should use a trigger, but implies
>> that this will change in the future.  I don't think we should change
>> that in the future; I think relying on triggers for that case is just
>> fine.  Note that it could be an issue with the post-image for UPDATES,
>> as well, and I think the trigger solution is similarly adequate to
>> cover that case.
>
> Hmm. I should not have written this in section of the current limitation.
> It may give impression the behavior will be changed future.
> OK, I'll try to revise the documentation stuff.
>
>>  With respect to the documented limitation regarding
>> DECLARE/FETCH, what exactly will happen?  Can we describe this a bit
>> more clearly rather than just saying the behavior will be
>> unpredictable?
>>
> In case when user-id was switched after declaration of a cursor that
> contains qualifier depending on current_user, its results set contains
> rows with old user-id and rows with new user-id.
>
> Here is one other option rather than documentation fix.
> As we had a discussion on the upthread, it can be solved if we restore
> the user-id associated with the portal to be run, however, a problem is
> some commands switches user-id inside of the portal.
> http://archives.postgresql.org/pgsql-hackers/2012-07/msg00055.php
>
> Is there some good idea to avoid the problem?
>
>> It looks suspiciously as if the row-level security mode needs to be
>> saved and restored in all the same places we call save and restore the
>> user ID and security context.  Is there some reason the
>> row-level-security-enabled flag shouldn't just become another bit in
>> the security context?  Then we'd get all of this save/restore logic
>> mostly for free.
>>
> It seems to me a good idea, but I didn't find out this.
>
>> ATExecSetRowLevelSecurity() calls SetRowLevelSecurity() or
>> ResetRowLevelSecurity() to update pg_rowlevelsec, but does the
>> pg_class update itself.  I think that all of this logic should be
>> moved into  a single function, or at least functions in the same file,
>> with the one that only updates pg_rowlevelsec being static and
>> therefore not able to be called from outside the file.  We always need
>> the pg_class update and the pg_rowlevelsec update to happen together,
>> so it's not good to have an exposed function that does one of those
>> updates but not the other.  I think the simplest thing is just to move
>> ATExecSetRowLevelSecurity to pg_rowlevelsec.c and rename it to
>> SetRowLevelSecurity() and then give it two static helper functions,
>> say InsertPolicyRow() and DeletePolicyRow().
>>
> OK, I'll rework the code.
>
>> I think it would be good if Tom could review the query-rewriting parts
>> of this (viz rowlevelsec.c) as I am not terribly familiar with this
>> machinery, and of course anything we get wrong here will have security
>> consequences.  At first blush, I'm somewhat concerned about the fact
>> that we're trying to do this after query rewriting; that seems like it
>> could break things.  I know KaiGai mentioned upthread that the
>> rewriter won't be rerun if the plan is invalidated, but (1) why is
>> that OK now? and (2) if it is OK now, then why is it OK to do
>> rewriting of the RLS qual - only - after rewriting if all of the rest
>> of the rewriting needs to happen earlier?
>>
> I just follow the existing behavior of plan invalidation; that does not
> re-run the query rewriter. So, if we have no particular reason why
> we should not run the rewriter again to handle RLS quals, it might
> be an option to handle RLS as a part of rewriter.
>
> At least, here is two problems. 1) System column is problematic
> when SELECT statement is replaced by sub-query. 2) It makes
> infinite recursion when a certain table has SELECT INSTEAD
> rule with a sub-query on the same table.
>
> Thanks,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.rw.v6.patch (134.0K, ../../CADyhKSUBRFuXpgy=NjMgX2H57aRh4pQ7cDhLcpn=ThCGCwaPgg@mail.gmail.com/2-pgsql-v9.3-row-level-security.rw.v6.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  59 ++
 doc/src/sgml/ref/alter_table.sgml          |  39 ++
 doc/src/sgml/user-manag.sgml               | 168 ++++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |  23 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/pg_rowlevelsec.c       | 288 +++++++++
 src/backend/commands/copy.c                |  91 ++-
 src/backend/commands/explain.c             |   8 +-
 src/backend/commands/tablecmds.c           |  27 +
 src/backend/nodes/nodeFuncs.c              |  12 +-
 src/backend/optimizer/plan/planner.c       |  16 +
 src/backend/optimizer/prep/preptlist.c     |  52 +-
 src/backend/optimizer/prep/prepunion.c     |  61 +-
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowlevelsec.c   | 685 +++++++++++++++++++++
 src/backend/parser/gram.y                  |  16 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  13 +-
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/pg_dump.c                  |  76 ++-
 src/bin/pg_dump/pg_dump.h                  |   1 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   3 +
 src/include/catalog/pg_class.h             |  20 +-
 src/include/catalog/pg_rowlevelsec.h       |  59 ++
 src/include/commands/copy.h                |   2 +-
 src/include/miscadmin.h                    |   1 +
 src/include/nodes/nodeFuncs.h              |   1 +
 src/include/nodes/parsenodes.h             |  12 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   2 +
 src/include/optimizer/rowlevelsec.h        |  25 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowlevelsec.out  | 929 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowlevelsec.sql       | 295 +++++++++
 45 files changed, 3039 insertions(+), 43 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f999190..55898a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 356419e..4f2cd37 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -808,6 +833,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..dab5b1e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,172 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3 t3_1
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Row-level security feature also works to queries for writer-
+   operations; such as <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It prevents to modify rows that does not satisfy the configured
+   row-level security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and the row-level security makes
+   sure any rows that don't match with <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   On the other hands, please note that row-level security is
+   not applied on <xref linkend="SQL-INSERT"> command, because
+   of no sense.
+   Any row-level security shall be applied on the timing when
+   rows are fetched from the tables, prior to evaluation of
+   user given <literal>WHERE</> clause to prevent unexpected
+   information leaks using non-leakproof functions.
+   All the rows to be inserted are. at least, visible for
+   current session users. therefore, it makes no sense to
+   check something from viewpoint of row-level security.
+  </para>
+  <para>
+   If you want to apply some constraints on rows to be inserted
+   or updated, we recommend to set up <literal>CHECK</>
+   constraints or before row triggers, rather than row-level
+   security features here.
+   Please also check the <xref linkend="rules-privileges">
+   section to understand leaky-view scenario bahalf on
+   the row-level security feature.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any
+   plan to allow individual row-level security policy for each
+   command type. Even if we want to perform with difference
+   policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause
+   can leak the rows to be invisible using
+   <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-level security,
+   please be careful if you plan to use <literal>current_user</>
+   in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it can fetch the first 100 rows with
+   privilege of <literal>alice</>, then remaining rows with
+   privilege of <literal>bob</>. If and when query execution plan
+   contains some kind of materialization and row-level security
+   policy contains <literal>current_user</>, the fetched tuples
+   in <literal>bob</>'s screen might be evaluated according to
+   the privilege of <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index b9cfee2..2142c36 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1240,6 +1241,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2291,6 +2296,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2940,6 +2948,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8818b68..cc56143 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..f0e49a6
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,288 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-level security policy from the catalog, and keep it on
+ * the relation cache.
+ */
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+/*
+ * Parse the supplied row-level security policy, and insert/update a row
+ * of pg_rowlevelsec catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * Remove row-level security policy row of pg_rowlevelsec
+ */
+static void
+DeletePolicyRow(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, clause);
+		class_form->relhasrowlevelsec = true;
+	}
+	else
+	{
+		DeletePolicyRow(relation);
+		class_form->relhasrowlevelsec = false;
+	}
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 10c89c7..7ffe4a3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowlevelsec.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
@@ -742,7 +747,7 @@ CopyLoadRawBuf(CopyState cstate)
  * the table or the specifically requested columns.
  */
 uint64
-DoCopy(const CopyStmt *stmt, const char *queryString)
+DoCopy(CopyStmt *stmt, const char *queryString)
 {
 	CopyState	cstate;
 	bool		is_from = stmt->is_from;
@@ -772,14 +777,26 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_level_security(stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			rel = NULL;
+		}
+		else
+		{
 		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) -
@@ -791,6 +808,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1139,6 +1157,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1210,6 +1275,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_LEVEL_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1234,6 +1318,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 33252a8..24756d0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1964,8 +1964,12 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 		case T_TidScan:
 		case T_ForeignScan:
 		case T_ModifyTable:
-			/* Assert it's on a real relation */
-			Assert(rte->rtekind == RTE_RELATION);
+			/*
+			 * Assert it's on either a real relation, or a sub-query of
+			 * row-level security being originated from a real relation.
+			 */
+			Assert((rte->rtekind == RTE_RELATION ||
+					rte->rtekind == RTE_SUBQUERY) && OidIsValid(rte->relid));
 			objectname = get_rel_name(rte->relid);
 			if (es->verbose)
 				namespace = get_namespace_name(get_rel_namespace(rte->relid));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f88bf79..3896522 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2749,6 +2750,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3112,6 +3115,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3391,6 +3396,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			SetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			SetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7552,6 +7563,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c52f4ed..8e04c78 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..c0416b5 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +246,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -393,6 +396,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-level security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-level security policy shall
+	 * be replaced with a RLS sub-query that has simple scan on the table
+	 * with security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark have
+	 * valid information.
+	 */
+	apply_row_level_security(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 1af4e7f..9bf7566 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,7 +37,44 @@
 
 static List *expand_targetlist(List *tlist, int command_type,
 				  Index result_relation, List *range_table);
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Vat that references sub-queries being originated from regular
+ * relations with row-level security policy.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
 
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on the pseudo targetList", attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -62,7 +99,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	{
 		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
 
-		if (rte->subquery != NULL || rte->relid == InvalidOid)
+		if (rte->subquery != NULL &&
+			rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY)
 			elog(ERROR, "subquery cannot be result relation");
 	}
 
@@ -95,7 +133,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -111,7 +150,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -129,7 +169,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -298,7 +338,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..e1ed690 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -1606,6 +1606,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
 									 QTW_IGNORE_RC_SUBQUERIES);
+
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
 			newnode->resultRelation = appinfo->child_relid;
@@ -1624,6 +1625,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rls_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d in relation %s",
+		 var->varattno, get_rel_name(rte->relid));
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1664,6 +1708,13 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+						var = (Var *)fixup_var_on_rls_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1759,15 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+											   parse->rtable);
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					return fixup_var_on_rls_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..1721bb5
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,685 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes TargetEntry that references underlying attribute. It may be
+ * Const node of dummy NULL, not Var node, if it is already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * append_pseudo_system_column
+ *
+ * It returns attribute number of pseudo-column relevant to the supplied
+ * Var-node referencing the RLS sub-query. If required attribute is not
+ * in target-list, it also adds a new pseudo-column.
+ */
+static AttrNumber
+append_pseudo_system_column(RangeTblEntry *rte, Var *var)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced system column is already attached on the target-
+		 * list of RLS sub-query, nothing to do here.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			if (var->varattno == subvar->varattno)
+			{
+				if (subtle->resjunk)
+					subtle->resjunk = false;
+				return subtle->resno;
+			}
+		}
+	}
+
+	/*
+	 * Here is no target-list for the referenced system column, so append
+	 * a new pseudo column on demand
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, var->varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varattno
+ *
+ * It recursively fixes up references to RLS sub-query, and adds pseudo-
+ * columns of underlying system columns, if necessary.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	bool	is_returning;
+} fixup_varattno_context;
+
+static bool
+fixup_varattno_walker(Node *node, fixup_varattno_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/* Var node does not reference Query node currently we focus on */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * All the regular columns should already have its own pseudo
+		 * column on expansion of RTE. Its resno of TargetEntry is
+		 * identical with underlying attribute, so never need to fix-up
+		 * varattno of Var node that references the sub-query.
+		 */
+		if (var->varattno > InvalidAttrNumber)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!context->is_returning &&
+			rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			/*
+			 * If this Var node is system-column or whole-row reference
+			 * on RLS sub-queries, its varattno has to be adjusted to
+			 * reference correct pseudo column. Pseudo column entries
+			 * of them are not constructed at expansion time, we append
+			 * it on demand.
+			 */
+			var->varattno = append_pseudo_system_column(rte, var);
+		}
+		else if (rte->rtekind == RTE_RELATION && rte->inh)
+		{
+			/*
+			 * Also, if this Var node is system-columns or whole-row
+			 * reference on the parent relation of inheritance tree that
+			 * includes RLS sub-queries, even though the parent relation
+			 * itself was not expanded, its pseudo-column entries have to
+			 * be added on the underlying child relations.
+			 * However, no need to fix up varattno of Var node, because
+			 * it shall be handled in prep/prepunion.c.
+			 */
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo *appinfo = lfirst(cell);
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				rte = rt_fetch(appinfo->child_relid,
+							   context->root->parse->rtable);
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					append_pseudo_system_column(rte, var);
+			}
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varattno_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varattno_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-level security configuration, if we try to expand
+ * the relation inside of RLS sub-query originated from same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * fixup_plan_rowmark
+ *
+ * Push down the given PlanRowMark into RLS sub-query.
+ */
+static void
+fixup_plan_rowmark(RangeTblEntry *rte, PlanRowMark *rowmark)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+
+	Assert(!rowmark->isParent);
+
+	if (rowmark->markType == ROW_MARK_EXCLUSIVE ||
+		rowmark->markType == ROW_MARK_SHARE)
+	{
+		RowMarkClause  *rclause = makeNode(RowMarkClause);
+
+		rclause->rti = (Index) 1;
+		if (rowmark->markType == ROW_MARK_EXCLUSIVE)
+			rclause->forUpdate = true;
+		else
+			rclause->forUpdate = false;
+		rclause->noWait = rowmark->noWait;
+		rclause->pushedDown = true;
+
+		subqry->rowMarks = lappend(subqry->rowMarks, rclause);
+	}
+	rowmark->markType = ROW_MARK_REFERENCE;
+
+	/*
+	 * Add 'ctid' and 'tableoid' pseudo columns to be required for
+	 * row-level locks
+	 */
+	subrte = rt_fetch(1, subqry->rtable);
+
+	subtle = make_pseudo_column(subrte, SelfItemPointerAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+
+	subtle = make_pseudo_column(subrte, TableOidAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+}
+
+/*
+ * expand_rtentry_rls
+ *
+ * It replaces the supplied RangeTblEntry (should be RTE_RELATION) by RLS
+ * sub-query with configured row-level security policy.
+ * This sub-query should have pseudo-column relevant to the regular columns,
+ * but no pseudo-columns for system-column or whole-row reference without
+ * references to them.
+ */
+static void
+expand_rtentry_rls(PlannerInfo *root, Index rtindex, Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construct pseudo columns as TargetEntry of sub-query that
+	 * references a particular regular attribute of the underlying
+	 * relation.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Replace the original RengeTblEntry by sub-query */
+	/* XXX - relid has to be kept */
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = subqry;
+	rte->security_barrier = true;
+
+	/* no permission checks on subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+
+	rte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	/*
+	 * Push-down of PlanRowMark if needed
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+		fixup_plan_rowmark(rte, rowmark);
+}
+
+/*
+ * pull_row_level_security
+ *
+ * It pulls the configured row-level security policy of both built-in
+ * and extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_level_security(Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-level security policy configured with built-in
+	 * features, if unprivileged users. Please note that superuser
+	 * can bypass it.
+	 */
+	if (relation->rlsdesc && !superuser())
+	{
+		RowLevelSecDesc *rlsdesc = relation->rlsdesc;
+
+		quals = copyObject(rlsdesc->rlsqual);
+		if (rlsdesc->rlshassublinks)
+			flags |= RLS_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-level security policy. If both built-in and extension
+	 * has their own policy, it shall be merged.
+	 */
+	if (rowlevel_security_hook)
+	{
+		List   *temp;
+
+		temp = (*rowlevel_security_hook)(relation);
+		if (temp != NIL)
+		{
+			if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_level_security
+ *
+ * It construct a RLS sub-query instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_level_security(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_level_security(rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_level_security
+ *
+ * Entrypoint to apply configured row-level security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-level
+ * security policy, its RangeTblEntry shall be replaced by a RLS sub-query
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the sub-query to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_level_security(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index		rtindex;
+	bool		has_rowlevel_security = false;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	for (rtindex = 1; rtindex <= list_length(parse->rtable); rtindex++)
+	{
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		Relation	rel;
+		Expr	   *quals;
+		int			flags;
+
+		/* only relation can have row-level security policy */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Parent relation of inheritance tree is just a placeholder here.
+		 * So, no need to apply row-level security.
+		 */
+		if (rte->inh)
+			continue;
+
+		/*
+		 * It does not make sense to check row-level security policy on
+		 * the target relation of INSERT command.
+		 */
+		if (parse->commandType == CMD_INSERT &&
+			parse->resultRelation == rtindex)
+			continue;
+
+		/*
+		 * It does not make sense to apply row-level security policy on
+		 * the relation we already handled.
+		 * Note that the underlying relation never have inh==true.
+		 */
+		if (parse->querySource == QSRC_ROW_LEVEL_SECURITY &&
+			rtindex == 1)
+			continue;
+
+		/*
+		 * OK, it is a reference to "real" relation. Let's try to apply
+		 * row-level security policy being configured, if any.
+		 */
+		rel = heap_open(rte->relid, NoLock);
+
+		quals = pull_row_level_security(rel, &flags);
+		if (quals)
+		{
+			expand_rtentry_rls(root, rtindex, quals, flags);
+			has_rowlevel_security = true;
+		}
+		heap_close(rel, NoLock);
+	}
+
+	if (has_rowlevel_security)
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varattno_context context;
+
+		/*
+		 * XXX - Constructed Plan with row-level security policy depends
+		 * on properties of current used (database superuser can bypass
+		 * configured RLS policy), thus, it has to be invalidated when
+		 * its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * XXX - varattno of Var node that references the RangeTblEntry
+		 * being replaced by RLS sub-query has to be adjusted for proper
+		 * reference to the underlying pseudo-column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.is_returning = false;
+		query_tree_walker(parse,
+						  fixup_varattno_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+		context.is_returning = true;
+		expression_tree_walker((Node *)parse->returningList,
+							   fixup_varattno_walker,
+							   (void *) &context);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e4ff76e..a3d445d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2127,6 +2127,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b75b2d9..9494889 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..12e7dd7 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2250,3 +2250,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..e54f92f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2999,6 +2999,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3078,8 +3079,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8c9ebe0..28ba831 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4162,6 +4176,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 82330cb..694fa95 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3908,6 +3908,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3932,7 +3933,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3952,7 +3991,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3988,7 +4028,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4023,7 +4064,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4058,7 +4100,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4094,7 +4137,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4129,7 +4173,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4160,7 +4205,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4186,7 +4232,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4222,7 +4269,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4270,6 +4318,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4312,6 +4361,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12847,6 +12900,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2100d43..57bd58b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -257,6 +257,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..41fcb21
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,59 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 8680ac3..1c49dfa 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -21,7 +21,7 @@
 /* CopyStateData is private in commands/copy.c */
 typedef struct CopyStateData *CopyState;
 
-extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
+extern uint64 DoCopy(CopyStmt *stmt, const char *queryString);
 
 extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
 extern CopyState BeginCopyFrom(Relation rel, const char *filename,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3ea3493..8d7a9ad 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -277,6 +277,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index e609e4b..a1d98e4 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8834499..a771727 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -700,6 +701,13 @@ typedef struct RangeTblEntry
 
 	/*
 	 * Fields valid for a plain relation RTE (else zero):
+	 *
+	 * XXX - Query optimizer may modify and replace RangeTblEntry on
+	 * a particular relation by sub-query, but should perform as result
+	 * relation of the query. In this case, relid field is used to track
+	 * which relation is the sub-query originated.
+	 * Right now, only row-level security feature uses this field to track
+	 * the relation-id of sub-query being originated.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
@@ -1233,6 +1241,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 0a1f8d5..b195dd7 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..c8e0019
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,25 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+extern bool	copy_row_level_security(CopyStmt *stmt,
+									Relation relation, List *attnums);
+extern void	 apply_row_level_security(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index aa9c648..3a1e4ea 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 50625d4..d470cad 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 extern Node *build_column_default(Relation rel, int attrno);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..ccaf17b
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,929 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+SET SESSION AUTHORIZATION rls_regress_user0;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+(12 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     Filter: f_leak(t1.b)
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     Filter: f_leak(t2.b)
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+                     Filter: f_leak(b)
+(15 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW LEVEL SECURITY (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW LEVEL SECURITY
+   (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(10 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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            
+---------------------------------
+ Result
+   ->  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)
+(8 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           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2
+               Filter: (a <= 2)
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 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           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2
+               Filter: (a = 2)
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(10 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
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+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
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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;
+ERROR:  variable not found in subplan target lists
+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
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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;
+ERROR:  variable not found in subplan target lists
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a |   b    
+---+--------
+ 1 | aaa
+ 3 | ccc
+ 2 | bcd
+ 4 | def
+ 1 | abcabc
+ 3 | cdecde
+ 1 | xxxxxx
+ 2 | yyyyyy
+ 3 | zzzzzz
+(9 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 663bf8a..4c4552f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index be789e3..eea4c3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a996b8c
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,295 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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 TABLE s2 SET ROW LEVEL SECURITY
+   (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;
+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;
+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;
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-11-25 14:20                                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:25                                                     ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-11-25 14:20 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

> However, UPDATE / DELETE support is not perfect right now.
> In case when we try to update / delete a table with inherited
> children and RETURNING clause was added, is loses right
> references to the pseudo columns, even though it works fine
> without inherited children.
>
The attached patch fixed this known problem.

I oversight that inheritance_planner() fixup Var->varno when
it references a sub-query; even if it originated from regular
table with row-level security policy.

In case when system-column or whole-row of the table with
row-level security policy referenced, rowlevelsec.c adds
relevant target-entries on the sub-query that wraps this
table-reference, and "varattno" of Var node towards system-
columns is adjusted later.
However, we need to treat RETURNING clause in a special way
because its Var node is evaluated at ExecUpdate or ExecDelete,
therefore, its attribute number should indicate raw-table, not
scanned virtual tuple on sub-query.

So, I added a logic to keep Var->varattno when it tries to reference
either system-column or whole-row of the replaced tables due to
row-level security.

Thanks,

2012/11/15 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> The attached patch is a revised version of row-level security
> feature.
> According to Robert's suggestion, I reworked implementation
> around ALTER command, and logic to disable RLS during
> FK/PK constraint checks.
>
> In addition, I moved the entrypoint to apply row-level security
> policy on the query tree next to the expand_inherited_tables,
> because it became clear my previous approach is not
> a straight-forward way to support update / delete cases.
>
> This patch performs to replace RangeTblEntry of tables with
> RLS policy by sub-queries that simply references the original
> table with configured RLS policy. Also, the sub-queries have
> security_barrier flag to prevent non-leakproof functions being
> pushed down from outside of the sub-query.
>
> This sub-query has target-list that just references columns of
> underlying table, and ordered according to column definition
> of the original table. So, we don't need to adjust varattno of
> Var-node that reference regular columns, even though the
> RangeTblEntry was replaced.
> On the other hand, system-column is problematic because
> sub-query does not have these columns due to nature of them.
> So, I inject a logic to adjust varattno of Var-node that references
> system-column of the target tables being replaced.
> It works fine as follows:
>
> postgres=> ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);
> ALTER TABLE
> postgres=> ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);
> ALTER TABLE
> postgres=> EXPLAIN (costs off) SELECT tableoid, * FROM t1 WHERE b like '%';
>                 QUERY PLAN
> -------------------------------------------
>  Result
>    ->  Append
>          ->  Subquery Scan on t1
>                Filter: (t1.b ~~ '%'::text)
>                ->  Seq Scan on t1 t1_1
>                      Filter: ((a % 2) = 0)
>          ->  Subquery Scan on t2
>                Filter: (t2.b ~~ '%'::text)
>                ->  Seq Scan on t2 t2_1
>                      Filter: ((a % 2) = 1)
>          ->  Seq Scan on t3
>                Filter: (b ~~ '%'::text)
> (12 rows)
>
> postgres=> SELECT tableoid, * FROM t1 WHERE b like '%';
>  tableoid | a  |  b
> ----------+----+-----
>     16385 |  2 | bbb
>     16385 |  4 | ddd
>     16385 |  6 | fff
>     16391 | 11 | sss
>     16391 | 13 | uuu
>     16391 | 15 | yyy
>     16397 | 21 | xyz
>     16397 | 22 | yzx
>     16397 | 23 | zxy
> (9 rows)
>
> Also, UPDATE / DELETE statement
>
> postgres=> EXPLAIN (costs off) UPDATE t1 SET b = b || '_updt' WHERE b like '%';
>              QUERY PLAN
> -------------------------------------
>  Update on t1
>    ->  Subquery Scan on t1
>          Filter: (t1.b ~~ '%'::text)
>          ->  Seq Scan on t1 t1_1
>                Filter: ((a % 2) = 0)
>    ->  Subquery Scan on t2
>          Filter: (t2.b ~~ '%'::text)
>          ->  Seq Scan on t2 t2_1
>                Filter: ((a % 2) = 1)
>    ->  Seq Scan on t3
>          Filter: (b ~~ '%'::text)
> (11 rows)
>
> postgres=> UPDATE t1 SET b = b || '_updt' WHERE b like '%';
> UPDATE 9
>
> However, UPDATE / DELETE support is not perfect right now.
> In case when we try to update / delete a table with inherited
> children and RETURNING clause was added, is loses right
> references to the pseudo columns, even though it works fine
> without inherited children.
>
> postgres=> UPDATE only t1 SET b = b || '_updt' WHERE b like '%' RETURNING *;
>  a |    b
> ---+----------
>  2 | bbb_updt
>  4 | ddd_updt
>  6 | fff_updt
> (3 rows)
>
> UPDATE 3
> postgres=> UPDATE t1 SET b = b || '_updt' WHERE b like '%' RETURNING *;
> ERROR:  variable not found in subplan target lists
>
> I'm still under investigation of this behavior. Any comments
> will be helpful to solve this problem.
>
> Thanks,
>
> 2012/10/22 Kohei KaiGai <kaigai@kaigai.gr.jp>:
>> 2012/10/22 Robert Haas <robertmhaas@gmail.com>:
>>> On Thu, Oct 18, 2012 at 2:19 PM, Alvaro Herrera
>>> <alvherre@2ndquadrant.com> wrote:
>>>> Kohei KaiGai escribió:
>>>>> The revised patch fixes the problem that Daen pointed out.
>>>>
>>>> Robert, would you be able to give this latest version of the patch a
>>>> look?
>>>
>>> Yeah, sorry I've been completely sidelined this CommitFest. It's been
>>> a crazy couple of months.  Prognosis for future craziness reduction
>>> uncertain.  Comments:
>>>
>>> The documentation lists several documented limitations that I would
>>> like to analyze a little bit.  First, it says that row-level security
>>> policies are not applied on UPDATE or DELETE.  That sounds downright
>>> dangerous to me.  Is there some really compelling reason we're not
>>> doing it?
>>
>> It intends to simplify the patch to avoid doing everything within a single
>> patch. I'll submit the patch supporting UPDATE and DELETE for CF-Nov
>> in addition to the base one.
>>
>>>  Second, it says that row-level security policies are not
>>> currently applied on INSERT, so you should use a trigger, but implies
>>> that this will change in the future.  I don't think we should change
>>> that in the future; I think relying on triggers for that case is just
>>> fine.  Note that it could be an issue with the post-image for UPDATES,
>>> as well, and I think the trigger solution is similarly adequate to
>>> cover that case.
>>
>> Hmm. I should not have written this in section of the current limitation.
>> It may give impression the behavior will be changed future.
>> OK, I'll try to revise the documentation stuff.
>>
>>>  With respect to the documented limitation regarding
>>> DECLARE/FETCH, what exactly will happen?  Can we describe this a bit
>>> more clearly rather than just saying the behavior will be
>>> unpredictable?
>>>
>> In case when user-id was switched after declaration of a cursor that
>> contains qualifier depending on current_user, its results set contains
>> rows with old user-id and rows with new user-id.
>>
>> Here is one other option rather than documentation fix.
>> As we had a discussion on the upthread, it can be solved if we restore
>> the user-id associated with the portal to be run, however, a problem is
>> some commands switches user-id inside of the portal.
>> http://archives.postgresql.org/pgsql-hackers/2012-07/msg00055.php
>>
>> Is there some good idea to avoid the problem?
>>
>>> It looks suspiciously as if the row-level security mode needs to be
>>> saved and restored in all the same places we call save and restore the
>>> user ID and security context.  Is there some reason the
>>> row-level-security-enabled flag shouldn't just become another bit in
>>> the security context?  Then we'd get all of this save/restore logic
>>> mostly for free.
>>>
>> It seems to me a good idea, but I didn't find out this.
>>
>>> ATExecSetRowLevelSecurity() calls SetRowLevelSecurity() or
>>> ResetRowLevelSecurity() to update pg_rowlevelsec, but does the
>>> pg_class update itself.  I think that all of this logic should be
>>> moved into  a single function, or at least functions in the same file,
>>> with the one that only updates pg_rowlevelsec being static and
>>> therefore not able to be called from outside the file.  We always need
>>> the pg_class update and the pg_rowlevelsec update to happen together,
>>> so it's not good to have an exposed function that does one of those
>>> updates but not the other.  I think the simplest thing is just to move
>>> ATExecSetRowLevelSecurity to pg_rowlevelsec.c and rename it to
>>> SetRowLevelSecurity() and then give it two static helper functions,
>>> say InsertPolicyRow() and DeletePolicyRow().
>>>
>> OK, I'll rework the code.
>>
>>> I think it would be good if Tom could review the query-rewriting parts
>>> of this (viz rowlevelsec.c) as I am not terribly familiar with this
>>> machinery, and of course anything we get wrong here will have security
>>> consequences.  At first blush, I'm somewhat concerned about the fact
>>> that we're trying to do this after query rewriting; that seems like it
>>> could break things.  I know KaiGai mentioned upthread that the
>>> rewriter won't be rerun if the plan is invalidated, but (1) why is
>>> that OK now? and (2) if it is OK now, then why is it OK to do
>>> rewriting of the RLS qual - only - after rewriting if all of the rest
>>> of the rewriting needs to happen earlier?
>>>
>> I just follow the existing behavior of plan invalidation; that does not
>> re-run the query rewriter. So, if we have no particular reason why
>> we should not run the rewriter again to handle RLS quals, it might
>> be an option to handle RLS as a part of rewriter.
>>
>> At least, here is two problems. 1) System column is problematic
>> when SELECT statement is replaced by sub-query. 2) It makes
>> infinite recursion when a certain table has SELECT INSTEAD
>> rule with a sub-query on the same table.
>>
>> Thanks,
>> --
>> KaiGai Kohei <kaigai@kaigai.gr.jp>
>
>
>
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

  [application/octet-stream] pgsql-v9.3-row-level-security.rw.v7.patch (136.4K, ../../CADyhKSU4jOAwZ-tWEeBUFzis2dN9NmwxWwLgYnHdfYWF25p5-Q@mail.gmail.com/2-pgsql-v9.3-row-level-security.rw.v7.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |   59 ++
 doc/src/sgml/ref/alter_table.sgml          |   39 ++
 doc/src/sgml/user-manag.sgml               |  168 +++++
 src/backend/catalog/Makefile               |    4 +-
 src/backend/catalog/dependency.c           |   23 +
 src/backend/catalog/heap.c                 |    1 +
 src/backend/catalog/pg_rowlevelsec.c       |  288 +++++++++
 src/backend/commands/copy.c                |   91 ++-
 src/backend/commands/explain.c             |    8 +-
 src/backend/commands/tablecmds.c           |   27 +
 src/backend/nodes/nodeFuncs.c              |   12 +-
 src/backend/optimizer/plan/planner.c       |   19 +-
 src/backend/optimizer/prep/preptlist.c     |   52 +-
 src/backend/optimizer/prep/prepunion.c     |   84 ++-
 src/backend/optimizer/util/Makefile        |    2 +-
 src/backend/optimizer/util/rowlevelsec.c   |  685 ++++++++++++++++++++
 src/backend/parser/gram.y                  |   16 +
 src/backend/parser/parse_agg.c             |    6 +
 src/backend/parser/parse_expr.c            |    3 +
 src/backend/rewrite/rewriteHandler.c       |   16 +
 src/backend/utils/adt/ri_triggers.c        |   13 +-
 src/backend/utils/cache/plancache.c        |   32 +
 src/backend/utils/cache/relcache.c         |   17 +-
 src/bin/pg_dump/pg_dump.c                  |   76 ++-
 src/bin/pg_dump/pg_dump.h                  |    1 +
 src/include/catalog/dependency.h           |    1 +
 src/include/catalog/indexing.h             |    3 +
 src/include/catalog/pg_class.h             |   20 +-
 src/include/catalog/pg_rowlevelsec.h       |   59 ++
 src/include/commands/copy.h                |    2 +-
 src/include/miscadmin.h                    |    1 +
 src/include/nodes/nodeFuncs.h              |    1 +
 src/include/nodes/parsenodes.h             |   12 +-
 src/include/nodes/plannodes.h              |    2 +
 src/include/nodes/relation.h               |    2 +
 src/include/optimizer/rowlevelsec.h        |   25 +
 src/include/parser/parse_node.h            |    3 +-
 src/include/rewrite/rewriteHandler.h       |    1 +
 src/include/utils/plancache.h              |    2 +
 src/include/utils/rel.h                    |    2 +
 src/test/regress/expected/rowlevelsec.out  |  954 ++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |    3 +-
 src/test/regress/parallel_schedule         |    2 +-
 src/test/regress/serial_schedule           |    1 +
 src/test/regress/sql/rowlevelsec.sql       |  295 +++++++++
 45 files changed, 3088 insertions(+), 45 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index f999190..55898a5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1807,6 +1812,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowlevelsec</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-level security policy; see
+       <link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -4906,6 +4921,50 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowlevelsec">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowlevelsec">
+   <primary>pg_rowlevelsec</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowlevelsec</structname> expression tree of
+   row-level security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowlevelsec</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rlsrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-level security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rlsqual</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-level security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 356419e..4f2cd37 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,6 +68,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW LEVEL SECURITY
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
@@ -567,6 +569,29 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW LEVEL SECURITY (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      See also <xref linkend="ROW-LEVEL-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW LEVEL SECURITY</literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -808,6 +833,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..dab5b1e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,172 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-level-security">
+  <title>Row-level Security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-level security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-level security policy can be set using
+   <literal>SET ROW LEVEL SECURITY</literal> command of
+   <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can
+    contain references to columns of the relation, so it enables
+    to construct arbitrary rule to make access control decision
+    based on contents of each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table
+   has <literal>uname</literal> field to store user name, and
+   it assume we don't want to expose any properties of other
+   customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-level security policy on the <literal>customer</literal>
+   table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW LEVEL SECURITY (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-level
+   security policy works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-level
+   security policy is implicitly added, and scan plan on the
+   target relation being wrapped up with a sub-query.
+   It ensures user given qualifiers, including functions with
+   side effects, are never executed earlier than the row-level
+   security policy regardless of its cost, except for the cases
+   when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order
+   to evaluate qualifiers is significant to keep confidentiality
+   of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass
+   checks with row-level security.
+   It ensures <application>pg_dump</application> can obtain
+   a complete set of database backup, and avoid to execute
+   Trojan horse trap, being injected as a row-level security
+   policy of user-defined table, with privileges of superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-level security
+   policy of the parent relation is not applied to child
+   relations. Scope of the row-level security policy is limited
+   to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+                QUERY PLAN
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.y)
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((x % 2) = 0)
+         ->  Seq Scan on t2
+               Filter: f_leak(y)
+         ->  Subquery Scan on t3
+               Filter: f_leak(t3.y)
+               ->  Seq Scan on t3 t3_1
+                     Filter: ((x % 2) = 1)
+(12 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-level security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-level security policy of <literal>t1</literal>,
+    <literal>x</literal> must be even-number, is appended only
+    <literal>t1</literal>, neither <literal>t2</literal> nor
+    <literal>t3</literal>. On the contrary, <literal>t3</literal>
+    has different row-level security policy; <literal>x</literal>
+    must be odd-number.
+  </para>
+
+  <para>
+   Row-level security feature also works to queries for writer-
+   operations; such as <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It prevents to modify rows that does not satisfy the configured
+   row-level security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and the row-level security makes
+   sure any rows that don't match with <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   On the other hands, please note that row-level security is
+   not applied on <xref linkend="SQL-INSERT"> command, because
+   of no sense.
+   Any row-level security shall be applied on the timing when
+   rows are fetched from the tables, prior to evaluation of
+   user given <literal>WHERE</> clause to prevent unexpected
+   information leaks using non-leakproof functions.
+   All the rows to be inserted are. at least, visible for
+   current session users. therefore, it makes no sense to
+   check something from viewpoint of row-level security.
+  </para>
+  <para>
+   If you want to apply some constraints on rows to be inserted
+   or updated, we recommend to set up <literal>CHECK</>
+   constraints or before row triggers, rather than row-level
+   security features here.
+   Please also check the <xref linkend="rules-privileges">
+   section to understand leaky-view scenario bahalf on
+   the row-level security feature.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any
+   plan to allow individual row-level security policy for each
+   command type. Even if we want to perform with difference
+   policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause
+   can leak the rows to be invisible using
+   <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-level security,
+   please be careful if you plan to use <literal>current_user</>
+   in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH">
+   allows to switch current user identifier during execution
+   of the query. Thus, it can fetch the first 100 rows with
+   privilege of <literal>alice</>, then remaining rows with
+   privilege of <literal>bob</>. If and when query execution plan
+   contains some kind of materialization and row-level security
+   policy contains <literal>current_user</>, the fetched tuples
+   in <literal>bob</>'s screen might be evaluated according to
+   the privilege of <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index df6da1f..965aa38 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -14,7 +14,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowlevelsec.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -38,7 +38,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_foreign_table.h pg_rowlevelsec.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/dependency.c b/src/backend/catalog/dependency.c
index b9cfee2..2142c36 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -1240,6 +1241,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveExtensionById(object->objectId);
 			break;
 
+		case OCLASS_ROWLEVELSEC:
+			RemoveRowLevelSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2291,6 +2296,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case RowLevelSecurityRelationId:
+			return OCLASS_ROWLEVELSEC;
 	}
 
 	/* shouldn't get here */
@@ -2940,6 +2948,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_ROWLEVELSEC:
+			{
+				char	   *relname;
+
+				relname = get_rel_name(object->objectId);
+				if (!relname)
+					elog(ERROR, "cache lookup failed for relation %u",
+						 object->objectId);
+				appendStringInfo(&buffer,
+								 _("row-level security of %s"), relname);
+
+
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8818b68..cc56143 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -776,6 +776,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowlevelsec - 1] = BoolGetDatum(rd_rel->relhasrowlevelsec);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
 	if (relacl != (Datum) 0)
diff --git a/src/backend/catalog/pg_rowlevelsec.c b/src/backend/catalog/pg_rowlevelsec.c
new file mode 100644
index 0000000..f0e49a6
--- /dev/null
+++ b/src/backend/catalog/pg_rowlevelsec.c
@@ -0,0 +1,288 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowlevelsec.c
+ *    routines to support manipulation of the pg_rowlevelsec relation
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-level security policy from the catalog, and keep it on
+ * the relation cache.
+ */
+void
+RelationBuildRowLevelSecurity(Relation relation)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, AccessShareLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+
+	tuple = systable_getnext(sscan);
+	if (HeapTupleIsValid(tuple))
+	{
+		RowLevelSecDesc	*rlsdesc;
+		MemoryContext	rlscxt;
+		MemoryContext	oldcxt;
+		Datum	datum;
+		bool	isnull;
+		char   *temp;
+
+		/*
+		 * Make the private memory context to store RowLevelSecDesc that
+		 * includes expression tree also.
+		 */
+		rlscxt = AllocSetContextCreate(CacheMemoryContext,
+									   RelationGetRelationName(relation),
+									   ALLOCSET_SMALL_MINSIZE,
+									   ALLOCSET_SMALL_INITSIZE,
+									   ALLOCSET_SMALL_MAXSIZE);
+		PG_TRY();
+		{
+			datum = heap_getattr(tuple, Anum_pg_rowlevelsec_rlsqual,
+								 RelationGetDescr(rlsrel), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(datum);
+
+			oldcxt = MemoryContextSwitchTo(rlscxt);
+
+			rlsdesc = palloc0(sizeof(RowLevelSecDesc));
+			rlsdesc->rlscxt = rlscxt;
+			rlsdesc->rlsqual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rlsdesc->rlsqual) == BOOLOID);
+
+			rlsdesc->rlshassublinks
+				= contain_subplans((Node *)rlsdesc->rlsqual);
+
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+		PG_CATCH();
+		{
+			MemoryContextDelete(rlscxt);
+			PG_RE_THROW();
+		}
+		PG_END_TRY();
+
+		relation->rlsdesc = rlsdesc;
+	}
+	else
+	{
+		relation->rlsdesc = NULL;
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, AccessShareLock);
+}
+
+/*
+ * Parse the supplied row-level security policy, and insert/update a row
+ * of pg_rowlevelsec catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		rlsrel;
+	ScanKeyData		skey;
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowlevelsec];
+	bool			isnull[Natts_pg_rowlevelsec];
+	bool			replaces[Natts_pg_rowlevelsec];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_LEVEL_SEC,
+								"ROW LEVEL SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowlevelsec catalog  */
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		replaces[Anum_pg_rowlevelsec_rlsqual - 1] = true;
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(rlsrel),
+								   values, isnull, replaces);
+		simple_heap_update(rlsrel, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowLevelSecurityRelationId,
+								   relationId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowlevelsec_rlsrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowlevelsec_rlsqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(rlsrel),
+								 values, isnull);
+		simple_heap_insert(rlsrel, newtup);
+	}
+	CatalogUpdateIndexes(rlsrel, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of RLS-policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowLevelSecurityRelationId;
+	myself.objectId = relationId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * Remove row-level security policy row of pg_rowlevelsec
+ */
+static void
+DeletePolicyRow(Relation relation)
+{
+	if (relation->rlsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowLevelSecurityRelationId;
+		address.objectId = RelationGetRelid(relation);
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-level security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-level security policy deletion.
+ */
+void
+RemoveRowLevelSecurityById(Oid relationId)
+{
+	Relation	rlsrel;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+
+	rlsrel = heap_open(RowLevelSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				Anum_pg_rowlevelsec_rlsrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(relationId));
+	sscan = systable_beginscan(rlsrel, RowLevelSecurityIndexId, true,
+							   SnapshotNow, 1, &skey);
+	while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+	{
+		simple_heap_delete(rlsrel, &tuple->t_self);
+	}
+	systable_endscan(sscan);
+	heap_close(rlsrel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW LEVEL SECURITY (...) OR
+ *                    RESET ROW LEVEL SECURITY
+ */
+void
+SetRowLevelSecurity(Relation relation, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	Relation	class_rel;
+	HeapTuple	tuple;
+	Form_pg_class	class_form;
+
+	class_rel = heap_open(RelationRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", relid);
+
+	class_form = (Form_pg_class) GETSTRUCT(tuple);
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, clause);
+		class_form->relhasrowlevelsec = true;
+	}
+	else
+	{
+		DeletePolicyRow(relation);
+		class_form->relhasrowlevelsec = false;
+	}
+	simple_heap_update(class_rel, &tuple->t_self, tuple);
+	CatalogUpdateIndexes(class_rel, tuple);
+
+	heap_close(class_rel, RowExclusiveLock);
+	heap_freetuple(tuple);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 10c89c7..7ffe4a3 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowlevelsec.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
@@ -742,7 +747,7 @@ CopyLoadRawBuf(CopyState cstate)
  * the table or the specifically requested columns.
  */
 uint64
-DoCopy(const CopyStmt *stmt, const char *queryString)
+DoCopy(CopyStmt *stmt, const char *queryString)
 {
 	CopyState	cstate;
 	bool		is_from = stmt->is_from;
@@ -772,14 +777,26 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_level_security(stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			rel = NULL;
+		}
+		else
+		{
 		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) -
@@ -791,6 +808,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1139,6 +1157,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1210,6 +1275,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_LEVEL_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1234,6 +1318,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 33252a8..24756d0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1964,8 +1964,12 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 		case T_TidScan:
 		case T_ForeignScan:
 		case T_ModifyTable:
-			/* Assert it's on a real relation */
-			Assert(rte->rtekind == RTE_RELATION);
+			/*
+			 * Assert it's on either a real relation, or a sub-query of
+			 * row-level security being originated from a real relation.
+			 */
+			Assert((rte->rtekind == RTE_RELATION ||
+					rte->rtekind == RTE_SUBQUERY) && OidIsValid(rte->relid));
 			objectname = get_rel_name(rte->relid);
 			if (es->verbose)
 				namespace = get_namespace_name(get_rel_namespace(rte->relid));
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f88bf79..3896522 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2749,6 +2750,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowLevelSecurity:
+			case AT_ResetRowLevelSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3112,6 +3115,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowLevelSecurity:
+		case AT_ResetRowLevelSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3391,6 +3396,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowLevelSecurity:
+			SetRowLevelSecurity(rel, (Node *) cmd->def);
+			break;
+		case AT_ResetRowLevelSecurity:
+			SetRowLevelSecurity(rel, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7552,6 +7563,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWLEVELSEC:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c52f4ed..8e04c78 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..7a4ba39 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -167,6 +168,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -244,6 +246,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -393,6 +396,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-level security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-level security policy shall
+	 * be replaced with a RLS sub-query that has simple scan on the table
+	 * with security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark have
+	 * valid information.
+	 */
+	apply_row_level_security(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -842,7 +858,8 @@ inheritance_planner(PlannerInfo *root)
 			{
 				RangeTblEntry *rte = (RangeTblEntry *) lfirst(lr);
 
-				if (rte->rtekind == RTE_SUBQUERY)
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY)
 				{
 					Index		newrti;
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 1af4e7f..9bf7566 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,7 +37,44 @@
 
 static List *expand_targetlist(List *tlist, int command_type,
 				  Index result_relation, List *range_table);
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Vat that references sub-queries being originated from regular
+ * relations with row-level security policy.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
 
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on the pseudo targetList", attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -62,7 +99,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	{
 		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
 
-		if (rte->subquery != NULL || rte->relid == InvalidOid)
+		if (rte->subquery != NULL &&
+			rte->subquery->querySource != QSRC_ROW_LEVEL_SECURITY)
 			elog(ERROR, "subquery cannot be result relation");
 	}
 
@@ -95,7 +133,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -111,7 +150,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -129,7 +169,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -298,7 +338,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..5b60c16 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,7 +1607,25 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * XXX - Returning clause should be handled in a special way.
+		 * In case when result relation of UPDATE / DELETE has row-level
+		 * security policy, its RangeTblEntry was replace by a sub-query,
+		 * thus, references to system-column need to be adjusted to point
+		 * pseudo-column behalf on the target system column.
+		 * However, Var nodes in returning clause are exception, because
+		 * its attribute number is evaluated towards the written image of
+		 * the tuple being updated or deleted, not virtual tuple of the
+		 * sub-query.
+		 */
+		context.in_returning = true;
+		newnode->returningList =
+			(List *) expression_tree_mutator((Node *) newnode->returningList,
+											 adjust_appendrel_attrs_mutator,
+											 (void *) &context);
+
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
 			newnode->resultRelation = appinfo->child_relid;
@@ -1624,6 +1644,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rls_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d in relation %s",
+		 var->varattno, get_rel_name(rte->relid));
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1664,6 +1727,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+						var = (Var *)fixup_var_on_rls_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1779,16 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+											   parse->rtable);
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					return fixup_var_on_rls_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3430689 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowlevelsec.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowlevelsec.c b/src/backend/optimizer/util/rowlevelsec.c
new file mode 100644
index 0000000..1721bb5
--- /dev/null
+++ b/src/backend/optimizer/util/rowlevelsec.c
@@ -0,0 +1,685 @@
+/*
+ * optimizer/util/rowlvsec.c
+ *    Row-level security support routines
+ *
+ * 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_rowlevelsec.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowlevelsec.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* flags to pull row-level security policy */
+#define RLS_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+rowlevel_security_hook_type	rowlevel_security_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes TargetEntry that references underlying attribute. It may be
+ * Const node of dummy NULL, not Var node, if it is already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * append_pseudo_system_column
+ *
+ * It returns attribute number of pseudo-column relevant to the supplied
+ * Var-node referencing the RLS sub-query. If required attribute is not
+ * in target-list, it also adds a new pseudo-column.
+ */
+static AttrNumber
+append_pseudo_system_column(RangeTblEntry *rte, Var *var)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY);
+
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced system column is already attached on the target-
+		 * list of RLS sub-query, nothing to do here.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			if (var->varattno == subvar->varattno)
+			{
+				if (subtle->resjunk)
+					subtle->resjunk = false;
+				return subtle->resno;
+			}
+		}
+	}
+
+	/*
+	 * Here is no target-list for the referenced system column, so append
+	 * a new pseudo column on demand
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, var->varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varattno
+ *
+ * It recursively fixes up references to RLS sub-query, and adds pseudo-
+ * columns of underlying system columns, if necessary.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	bool	is_returning;
+} fixup_varattno_context;
+
+static bool
+fixup_varattno_walker(Node *node, fixup_varattno_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/* Var node does not reference Query node currently we focus on */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * All the regular columns should already have its own pseudo
+		 * column on expansion of RTE. Its resno of TargetEntry is
+		 * identical with underlying attribute, so never need to fix-up
+		 * varattno of Var node that references the sub-query.
+		 */
+		if (var->varattno > InvalidAttrNumber)
+			return false;
+
+		rte = rt_fetch(var->varno, context->root->parse->rtable);
+		if (!context->is_returning &&
+			rte->rtekind == RTE_SUBQUERY &&
+			rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+		{
+			/*
+			 * If this Var node is system-column or whole-row reference
+			 * on RLS sub-queries, its varattno has to be adjusted to
+			 * reference correct pseudo column. Pseudo column entries
+			 * of them are not constructed at expansion time, we append
+			 * it on demand.
+			 */
+			var->varattno = append_pseudo_system_column(rte, var);
+		}
+		else if (rte->rtekind == RTE_RELATION && rte->inh)
+		{
+			/*
+			 * Also, if this Var node is system-columns or whole-row
+			 * reference on the parent relation of inheritance tree that
+			 * includes RLS sub-queries, even though the parent relation
+			 * itself was not expanded, its pseudo-column entries have to
+			 * be added on the underlying child relations.
+			 * However, no need to fix up varattno of Var node, because
+			 * it shall be handled in prep/prepunion.c.
+			 */
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo *appinfo = lfirst(cell);
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				rte = rt_fetch(appinfo->child_relid,
+							   context->root->parse->rtable);
+				if (rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_LEVEL_SECURITY)
+					append_pseudo_system_column(rte, var);
+			}
+		}
+		return false;
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varattno_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varattno_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-level security configuration, if we try to expand
+ * the relation inside of RLS sub-query originated from same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_LEVEL_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * fixup_plan_rowmark
+ *
+ * Push down the given PlanRowMark into RLS sub-query.
+ */
+static void
+fixup_plan_rowmark(RangeTblEntry *rte, PlanRowMark *rowmark)
+{
+	Query		   *subqry = rte->subquery;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+
+	Assert(!rowmark->isParent);
+
+	if (rowmark->markType == ROW_MARK_EXCLUSIVE ||
+		rowmark->markType == ROW_MARK_SHARE)
+	{
+		RowMarkClause  *rclause = makeNode(RowMarkClause);
+
+		rclause->rti = (Index) 1;
+		if (rowmark->markType == ROW_MARK_EXCLUSIVE)
+			rclause->forUpdate = true;
+		else
+			rclause->forUpdate = false;
+		rclause->noWait = rowmark->noWait;
+		rclause->pushedDown = true;
+
+		subqry->rowMarks = lappend(subqry->rowMarks, rclause);
+	}
+	rowmark->markType = ROW_MARK_REFERENCE;
+
+	/*
+	 * Add 'ctid' and 'tableoid' pseudo columns to be required for
+	 * row-level locks
+	 */
+	subrte = rt_fetch(1, subqry->rtable);
+
+	subtle = make_pseudo_column(subrte, SelfItemPointerAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+
+	subtle = make_pseudo_column(subrte, TableOidAttributeNumber);
+	subtle->resno = list_length(subqry->targetList) + 1;
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+		makeString(pstrdup(subtle->resname)));
+}
+
+/*
+ * expand_rtentry_rls
+ *
+ * It replaces the supplied RangeTblEntry (should be RTE_RELATION) by RLS
+ * sub-query with configured row-level security policy.
+ * This sub-query should have pseudo-column relevant to the regular columns,
+ * but no pseudo-columns for system-column or whole-row reference without
+ * references to them.
+ */
+static void
+expand_rtentry_rls(PlannerInfo *root, Index rtindex, Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construct pseudo columns as TargetEntry of sub-query that
+	 * references a particular regular attribute of the underlying
+	 * relation.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Replace the original RengeTblEntry by sub-query */
+	/* XXX - relid has to be kept */
+	rte->rtekind = RTE_SUBQUERY;
+	rte->subquery = subqry;
+	rte->security_barrier = true;
+
+	/* no permission checks on subquery itself */
+	rte->requiredPerms = 0;
+	rte->checkAsUser = InvalidOid;
+	rte->selectedCols = NULL;
+	rte->modifiedCols = NULL;
+
+	rte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	/*
+	 * Push-down of PlanRowMark if needed
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+		fixup_plan_rowmark(rte, rowmark);
+}
+
+/*
+ * pull_row_level_security
+ *
+ * It pulls the configured row-level security policy of both built-in
+ * and extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_level_security(Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-level security policy configured with built-in
+	 * features, if unprivileged users. Please note that superuser
+	 * can bypass it.
+	 */
+	if (relation->rlsdesc && !superuser())
+	{
+		RowLevelSecDesc *rlsdesc = relation->rlsdesc;
+
+		quals = copyObject(rlsdesc->rlsqual);
+		if (rlsdesc->rlshassublinks)
+			flags |= RLS_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-level security policy. If both built-in and extension
+	 * has their own policy, it shall be merged.
+	 */
+	if (rowlevel_security_hook)
+	{
+		List   *temp;
+
+		temp = (*rowlevel_security_hook)(relation);
+		if (temp != NIL)
+		{
+			if ((flags & RLS_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RLS_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_level_security
+ *
+ * It construct a RLS sub-query instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_level_security(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_level_security(rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_LEVEL_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RLS_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_level_security
+ *
+ * Entrypoint to apply configured row-level security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-level
+ * security policy, its RangeTblEntry shall be replaced by a RLS sub-query
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the sub-query to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_level_security(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index		rtindex;
+	bool		has_rowlevel_security = false;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	for (rtindex = 1; rtindex <= list_length(parse->rtable); rtindex++)
+	{
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		Relation	rel;
+		Expr	   *quals;
+		int			flags;
+
+		/* only relation can have row-level security policy */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Parent relation of inheritance tree is just a placeholder here.
+		 * So, no need to apply row-level security.
+		 */
+		if (rte->inh)
+			continue;
+
+		/*
+		 * It does not make sense to check row-level security policy on
+		 * the target relation of INSERT command.
+		 */
+		if (parse->commandType == CMD_INSERT &&
+			parse->resultRelation == rtindex)
+			continue;
+
+		/*
+		 * It does not make sense to apply row-level security policy on
+		 * the relation we already handled.
+		 * Note that the underlying relation never have inh==true.
+		 */
+		if (parse->querySource == QSRC_ROW_LEVEL_SECURITY &&
+			rtindex == 1)
+			continue;
+
+		/*
+		 * OK, it is a reference to "real" relation. Let's try to apply
+		 * row-level security policy being configured, if any.
+		 */
+		rel = heap_open(rte->relid, NoLock);
+
+		quals = pull_row_level_security(rel, &flags);
+		if (quals)
+		{
+			expand_rtentry_rls(root, rtindex, quals, flags);
+			has_rowlevel_security = true;
+		}
+		heap_close(rel, NoLock);
+	}
+
+	if (has_rowlevel_security)
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varattno_context context;
+
+		/*
+		 * XXX - Constructed Plan with row-level security policy depends
+		 * on properties of current used (database superuser can bypass
+		 * configured RLS policy), thus, it has to be invalidated when
+		 * its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * XXX - varattno of Var node that references the RangeTblEntry
+		 * being replaced by RLS sub-query has to be adjusted for proper
+		 * reference to the underlying pseudo-column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.is_returning = false;
+		query_tree_walker(parse,
+						  fixup_varattno_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+		context.is_returning = true;
+		expression_tree_walker((Node *)parse->returningList,
+							   fixup_varattno_walker,
+							   (void *) &context);
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e4ff76e..a3d445d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -2127,6 +2127,22 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW LEVEL SECURITY (expression) */
+			| SET ROW LEVEL SECURITY '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowLevelSecurity;
+					n->def = (Node *) $6;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW LEVEL SECURITY */
+			| RESET ROW LEVEL SECURITY
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowLevelSecurity;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index b75b2d9..9494889 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("aggregate functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("aggregate functions are not allowed in row-level security");
+			break;
 
 			/*
 			 * There is intentionally no default: case here, so that the
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		case EXPR_KIND_TRIGGER_WHEN:
 			err = _("window functions are not allowed in trigger WHEN conditions");
 			break;
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			err = _("window functions are not allowed in row-level security");
+			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
index e9267c5..94c25d1 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1443,6 +1443,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_LEVEL_SEC:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2609,6 +2610,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			return "EXECUTE";
 		case EXPR_KIND_TRIGGER_WHEN:
 			return "WHEN";
+		case EXPR_KIND_ROW_LEVEL_SEC:
+			return "ROW LEVEL SECURITY";
 
 			/*
 			 * There is intentionally no default: case here, so that the
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..12e7dd7 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2250,3 +2250,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 97e68b1..e54f92f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -2999,6 +2999,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3078,8 +3079,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 8c0391f..36a8750 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -51,6 +51,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -665,6 +666,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -716,6 +727,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -794,6 +807,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	PopOverrideSearchPath();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Make a dedicated memory context for the CachedPlan and its subsidiary
 	 * data.  It's probably not going to be large, but just in case, use the
 	 * default maxsize parameter.  It's transient for the moment.
@@ -828,6 +859,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->context = plan_context;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8c9ebe0..28ba831 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -49,6 +49,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -896,6 +897,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowlevelsec)
+		RelationBuildRowLevelSecurity(relation);
+	else
+		relation->rlsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1785,6 +1791,8 @@ RelationDestroyRelation(Relation relation)
 		MemoryContextDelete(relation->rd_indexcxt);
 	if (relation->rd_rulescxt)
 		MemoryContextDelete(relation->rd_rulescxt);
+	if (relation->rlsdesc)
+		MemoryContextDelete(relation->rlsdesc->rlscxt);
 	pfree(relation);
 }
 
@@ -3024,7 +3032,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			restart = true;
 		}
-
+		if (relation->rd_rel->relhasrowlevelsec && relation->rlsdesc == NULL)
+		{
+			RelationBuildRowLevelSecurity(relation);
+			if (relation->rlsdesc == NULL)
+				relation->rd_rel->relhasrowlevelsec = false;
+			restart = true;
+		}
 		/* Release hold on the relation */
 		RelationDecrementReferenceCount(relation);
 
@@ -4162,6 +4176,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_rules = NULL;
 		rel->rd_rulescxt = NULL;
 		rel->trigdesc = NULL;
+		rel->rlsdesc = NULL;
 		rel->rd_indexprs = NIL;
 		rel->rd_indpred = NIL;
 		rel->rd_exclops = NULL;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 82330cb..694fa95 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3908,6 +3908,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_reloptions;
 	int			i_toastreloptions;
 	int			i_reloftype;
+	int			i_rlsqual;
 
 	/* Make sure we are in proper schema */
 	selectSourceSchema(fout, "pg_catalog");
@@ -3932,7 +3933,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90100)
+	if (fout->remoteVersion >= 90300)
+	{
+		/*
+		 * 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, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "pg_catalog.pg_get_expr(rls.rlsqual, rls.rlsrelid) AS rlsqual "
+						  "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) "
+					   "LEFT JOIN pg_rowlevelsec rls ON (c.oid = rls.rlsrelid) "
+						  "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -3952,7 +3991,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL as rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -3988,7 +4028,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4023,7 +4064,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions "
+						  "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4058,7 +4100,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						"array_to_string(c.reloptions, ', ') AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4094,7 +4137,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4129,7 +4173,8 @@ getTables(Archive *fout, int *numTables)
 						  "d.refobjsubid AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "LEFT JOIN pg_depend d ON "
 						  "(c.relkind = '%c' AND "
@@ -4160,7 +4205,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4186,7 +4232,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class "
 						  "WHERE relkind IN ('%c', '%c', '%c') "
 						  "ORDER BY oid",
@@ -4222,7 +4269,8 @@ getTables(Archive *fout, int *numTables)
 						  "NULL::int4 AS owning_col, "
 						  "NULL AS reltablespace, "
 						  "NULL AS reloptions, "
-						  "NULL AS toast_reloptions "
+						  "NULL AS toast_reloptions, "
+						  "NULL AS rlsqual "
 						  "FROM pg_class c "
 						  "WHERE relkind IN ('%c', '%c') "
 						  "ORDER BY oid",
@@ -4270,6 +4318,7 @@ getTables(Archive *fout, int *numTables)
 	i_reloptions = PQfnumber(res, "reloptions");
 	i_toastreloptions = PQfnumber(res, "toast_reloptions");
 	i_reloftype = PQfnumber(res, "reloftype");
+	i_rlsqual = PQfnumber(res, "rlsqual");
 
 	if (lockWaitTimeout && fout->remoteVersion >= 70300)
 	{
@@ -4312,6 +4361,10 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].reloftype = NULL;
 		else
 			tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
+		if (PQgetisnull(res, i, i_rlsqual))
+			tblinfo[i].rlsqual = NULL;
+		else
+			tblinfo[i].rlsqual = pg_strdup(PQgetvalue(res, i, i_rlsqual));
 		tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
 		if (PQgetisnull(res, i, i_owning_tab))
 		{
@@ -12847,6 +12900,9 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			}
 		}
 	}
+	if (tbinfo->rlsqual)
+		appendPQExpBuffer(q, "ALTER TABLE ONLY %s SET ROW LEVEL SECURITY %s;\n",
+						  fmtId(tbinfo->dobj.name), tbinfo->rlsqual);
 
 	if (binary_upgrade)
 		binary_upgrade_extension_member(q, &tbinfo->dobj, labelq->data);
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2100d43..57bd58b 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -257,6 +257,7 @@ typedef struct _tableInfo
 	uint32		toast_frozenxid;	/* for restore toast frozen xid */
 	int			ncheck;			/* # of CHECK expressions */
 	char	   *reloftype;		/* underlying type for typed table */
+	char	   *rlsqual;		/* row-level security policy */
 	/* these two are set only if table is a sequence owned by a column: */
 	Oid			owning_tab;		/* OID of table owning sequence */
 	int			owning_col;		/* attr # of column owning sequence */
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8499768..49baa0e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_ROWLEVELSEC,			/* pg_rowlevelsec */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 238fe58..a3b07e2 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,9 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_rowlevelsec_relid_index, 3839, on pg_rowlevelsec using btree(rlsrelid oid_ops));
+#define RowLevelSecurityIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index f83ce80..5b6e38b 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowlevelsec;	/* has (or has had) row-level security */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
 
@@ -91,7 +92,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					27
+#define Natts_pg_class					28
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -115,10 +116,11 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relfrozenxid		25
-#define Anum_pg_class_relacl			26
-#define Anum_pg_class_reloptions		27
+#define Anum_pg_class_relhasrowlevelsec	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relfrozenxid		26
+#define Anum_pg_class_relacl			27
+#define Anum_pg_class_reloptions		28
 
 /* ----------------
  *		initial contents of pg_class
@@ -130,13 +132,13 @@ typedef FormData_pg_class *Form_pg_class;
  */
 
 /* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */
-DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f 3 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f 3 _null_ _null_ ));
 DESCR("");
 
 
diff --git a/src/include/catalog/pg_rowlevelsec.h b/src/include/catalog/pg_rowlevelsec.h
new file mode 100644
index 0000000..41fcb21
--- /dev/null
+++ b/src/include/catalog/pg_rowlevelsec.h
@@ -0,0 +1,59 @@
+/*
+ * pg_rowlevelsec.h
+ *   definition of the system catalog for row-level security policy
+ *   (pg_rowlevelsec)
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ */
+#ifndef PG_ROWLEVELSEC_H
+#define PG_ROWLEVELSEC_H
+
+#include "catalog/genbki.h"
+#include "nodes/primnodes.h"
+#include "utils/memutils.h"
+#include "utils/relcache.h"
+
+/* ----------------
+ *		pg_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowLevelSecurityRelationId	3838
+
+CATALOG(pg_rowlevelsec,3838) BKI_WITHOUT_OIDS
+{
+	Oid				rlsrelid;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rlsqual;
+#endif
+} FormData_pg_rowlevelsec;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowlevelsec *Form_pg_rowlevelsec;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowlevelsec				2
+#define Anum_pg_rowlevelsec_rlsrelid		1
+#define Anum_pg_rowlevelsec_rlsqual			2
+
+typedef struct
+{
+	MemoryContext	rlscxt;
+	Expr		   *rlsqual;
+	bool			rlshassublinks;
+} RowLevelSecDesc;
+
+extern void	RelationBuildRowLevelSecurity(Relation relation);
+extern void	SetRowLevelSecurity(Relation relation, Node *clause);
+extern void	RemoveRowLevelSecurityById(Oid relationId);
+
+#endif  /* PG_ROWLEVELSEC_H */
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index 8680ac3..1c49dfa 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -21,7 +21,7 @@
 /* CopyStateData is private in commands/copy.c */
 typedef struct CopyStateData *CopyState;
 
-extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
+extern uint64 DoCopy(CopyStmt *stmt, const char *queryString);
 
 extern void ProcessCopyOptions(CopyState cstate, bool is_from, List *options);
 extern CopyState BeginCopyFrom(Relation rel, const char *filename,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 3ea3493..8d7a9ad 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -277,6 +277,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index e609e4b..a1d98e4 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8834499..a771727 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_LEVEL_SECURITY,	/* added by row-level security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -700,6 +701,13 @@ typedef struct RangeTblEntry
 
 	/*
 	 * Fields valid for a plain relation RTE (else zero):
+	 *
+	 * XXX - Query optimizer may modify and replace RangeTblEntry on
+	 * a particular relation by sub-query, but should perform as result
+	 * relation of the query. In this case, relid field is used to track
+	 * which relation is the sub-query originated.
+	 * Right now, only row-level security feature uses this field to track
+	 * the relation-id of sub-query being originated.
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
@@ -1233,6 +1241,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowLevelSecurity,		/* SET ROW LEVEL SECURITY (...) */
+	AT_ResetRowLevelSecurity,	/* RESET ROW LEVEL SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..6b3ea3d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 0a1f8d5..b195dd7 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/optimizer/rowlevelsec.h b/src/include/optimizer/rowlevelsec.h
new file mode 100644
index 0000000..c8e0019
--- /dev/null
+++ b/src/include/optimizer/rowlevelsec.h
@@ -0,0 +1,25 @@
+/* -------------------------------------------------------------------------
+ *
+ * rowlevelsec.h
+ *    prototypes for optimizer/rowlevelsec.c
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef ROWLEVELSEC_H
+#define ROWLEVELSEC_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
+#include "utils/rel.h"
+
+typedef List *(*rowlevel_security_hook_type)(Relation relation);
+extern PGDLLIMPORT rowlevel_security_hook_type rowlevel_security_hook;
+
+extern bool	copy_row_level_security(CopyStmt *stmt,
+									Relation relation, List *attnums);
+extern void	 apply_row_level_security(PlannerInfo *root);
+
+#endif	/* ROWLEVELSEC_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index aa9c648..3a1e4ea 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,			/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_LEVEL_SEC,		/* policy of ROW LEVEL SECURITY */
 } ParseExprKind;
 
 
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 50625d4..d470cad 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 extern Node *build_column_default(Relation rel, int attrno);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 413e846..5f89028 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -115,6 +115,8 @@ typedef struct CachedPlan
 								 * bare utility statements) */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 4669d8a..dce8463 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_index.h"
+#include "catalog/pg_rowlevelsec.h"
 #include "fmgr.h"
 #include "nodes/bitmapset.h"
 #include "rewrite/prs2lock.h"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
+	RowLevelSecDesc	*rlsdesc;	/* Row-level security info, or NULL */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowlevelsec.out b/src/test/regress/expected/rowlevelsec.out
new file mode 100644
index 0000000..854acb0
--- /dev/null
+++ b/src/test/regress/expected/rowlevelsec.out
@@ -0,0 +1,954 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+SET SESSION AUTHORIZATION rls_regress_user0;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (a % 2 = 1);	-- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+                QUERY PLAN                 
+-------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+(12 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                      QUERY PLAN                       
+-------------------------------------------------------
+ LockRows
+   ->  Result
+         ->  Append
+               ->  Subquery Scan on t1
+                     Filter: f_leak(t1.b)
+                     ->  LockRows
+                           ->  Seq Scan on t1 t1_1
+                                 Filter: ((a % 2) = 0)
+               ->  Subquery Scan on t2
+                     Filter: f_leak(t2.b)
+                     ->  LockRows
+                           ->  Seq Scan on t2 t2_1
+                                 Filter: ((a % 2) = 1)
+               ->  Seq Scan on t3
+                     Filter: f_leak(b)
+(15 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW LEVEL SECURITY (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW LEVEL SECURITY
+   (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a <= 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a <= 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(10 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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            
+---------------------------------
+ Result
+   ->  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)
+(8 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           
+--------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a <= 2)
+         ->  Seq Scan on t2
+               Filter: (a <= 2)
+         ->  Seq Scan on t3
+               Filter: (a <= 2)
+(8 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           
+-------------------------------
+ Result
+   ->  Append
+         ->  Seq Scan on t1
+               Filter: (a = 2)
+         ->  Seq Scan on t2
+               Filter: (a = 2)
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(8 rows)
+
+-- also, case when privilege switch from superuser
+SET SESSION AUTHORIZATION rls_regress_user0;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Result
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a = 2) AND ((a % 2) = 0))
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a = 2) AND ((a % 2) = 1))
+         ->  Seq Scan on t3
+               Filter: (a = 2)
+(10 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
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+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
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 1 | aaa
+ 3 | ccc
+ 2 | bcd
+ 4 | def
+(4 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 3f04442..ffcd8d3 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowlevelsec          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 663bf8a..4c4552f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate
+test: privileges rowlevelsec security_label collate
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index be789e3..eea4c3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -92,6 +92,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowlevelsec
 test: security_label
 test: collate
 test: misc
diff --git a/src/test/regress/sql/rowlevelsec.sql b/src/test/regress/sql/rowlevelsec.sql
new file mode 100644
index 0000000..a996b8c
--- /dev/null
+++ b/src/test/regress/sql/rowlevelsec.sql
@@ -0,0 +1,295 @@
+--
+-- 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 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 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
+CREATE TABLE uaccount (
+    pguser      name primary key,
+    seclv       int
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW LEVEL SECURITY
+    (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW LEVEL SECURITY (true);     -- fail
+ALTER TABLE document RESET ROW LEVEL SECURITY;          -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW LEVEL SECURITY (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW LEVEL SECURITY
+    (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW LEVEL SECURITY (a % 2 = 0);	-- be even number
+ALTER TABLE t2 SET ROW LEVEL SECURITY (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY
+   (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW LEVEL SECURITY (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW LEVEL SECURITY
+   (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 TABLE s2 SET ROW LEVEL SECURITY
+   (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;
+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;
+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;
+
+RESET SESSION AUTHORIZATION;
+SELECT * FROM t1;
+
+--
+-- 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-12-03 15:25                                                     ` David Fetter <david@fetter.org>
  2012-12-03 15:36                                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: David Fetter @ 2012-12-03 15:25 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Sun, Nov 25, 2012 at 03:20:28PM +0100, Kohei KaiGai wrote:
> > However, UPDATE / DELETE support is not perfect right now.
> > In case when we try to update / delete a table with inherited
> > children and RETURNING clause was added, is loses right
> > references to the pseudo columns, even though it works fine
> > without inherited children.
> >
> The attached patch fixed this known problem.

This patch no longer applies to git master.  Any chance of a rebase?

Also, might this approach work for the catalog?  The use case I have
in mind is multi-tenancy, although one can imagine organizations where
internal access controls might be required on it, too.

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:25                                                     ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
@ 2012-12-03 15:36                                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:41                                                         ` Re: [v9.3] Row-Level Security Simon Riggs <simon@2ndQuadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2012-12-03 15:36 UTC (permalink / raw)
  To: David Fetter <david@fetter.org>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

2012/12/3 David Fetter <david@fetter.org>:
> On Sun, Nov 25, 2012 at 03:20:28PM +0100, Kohei KaiGai wrote:
>> > However, UPDATE / DELETE support is not perfect right now.
>> > In case when we try to update / delete a table with inherited
>> > children and RETURNING clause was added, is loses right
>> > references to the pseudo columns, even though it works fine
>> > without inherited children.
>> >
>> The attached patch fixed this known problem.
>
> This patch no longer applies to git master.  Any chance of a rebase?
>
OK, I'll rebese it.

> Also, might this approach work for the catalog?  The use case I have
> in mind is multi-tenancy, although one can imagine organizations where
> internal access controls might be required on it, too.
>
If you intend to control behavior of DDL commands that internally takes
access towards system catalog, RLS feature is not helpful.
Please use sepgsql instead. :-)
If you intend to control DML commands towards system catalogs, here
is nothing special, so I expect it works as we are doing at user tables.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:25                                                     ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
  2012-12-03 15:36                                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-12-03 15:41                                                         ` Simon Riggs <simon@2ndQuadrant.com>
  2012-12-03 16:09                                                           ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
  0 siblings, 1 reply; 165+ messages in thread

From: Simon Riggs @ 2012-12-03 15:41 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: David Fetter <david@fetter.org>; Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On 3 December 2012 15:36, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> 2012/12/3 David Fetter <david@fetter.org>:
>> On Sun, Nov 25, 2012 at 03:20:28PM +0100, Kohei KaiGai wrote:
>>> > However, UPDATE / DELETE support is not perfect right now.
>>> > In case when we try to update / delete a table with inherited
>>> > children and RETURNING clause was added, is loses right
>>> > references to the pseudo columns, even though it works fine
>>> > without inherited children.
>>> >
>>> The attached patch fixed this known problem.
>>
>> This patch no longer applies to git master.  Any chance of a rebase?
>>
> OK, I'll rebese it.

No chunk failures, its just fuzz.

-- 
 Simon Riggs                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:25                                                     ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
  2012-12-03 15:36                                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:41                                                         ` Re: [v9.3] Row-Level Security Simon Riggs <simon@2ndQuadrant.com>
@ 2012-12-03 16:09                                                           ` David Fetter <david@fetter.org>
  2012-12-03 16:59                                                             ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: David Fetter @ 2012-12-03 16:09 UTC (permalink / raw)
  To: Simon Riggs <simon@2ndQuadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On Mon, Dec 03, 2012 at 03:41:47PM +0000, Simon Riggs wrote:
> On 3 December 2012 15:36, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> > 2012/12/3 David Fetter <david@fetter.org>:
> >> On Sun, Nov 25, 2012 at 03:20:28PM +0100, Kohei KaiGai wrote:
> >>> > However, UPDATE / DELETE support is not perfect right now.
> >>> > In case when we try to update / delete a table with inherited
> >>> > children and RETURNING clause was added, is loses right
> >>> > references to the pseudo columns, even though it works fine
> >>> > without inherited children.
> >>> >
> >>> The attached patch fixed this known problem.
> >>
> >> This patch no longer applies to git master.  Any chance of a rebase?
> >>
> > OK, I'll rebese it.
> 
> No chunk failures, its just fuzz.

I must have done something wrong.

I downloaded the patch from the web email archives, then ran "git
apply" on it, and got this:

$ git apply ../pgsql-v9.3-row-level-security.rw.v7.patch
../pgsql-v9.3-row-level-security.rw.v7.patch:806: space before tab in indent.
         * row-level security policy
../pgsql-v9.3-row-level-security.rw.v7.patch:1909: trailing whitespace.
 * would be given. Because the sub-query has security barrier flag, 
../pgsql-v9.3-row-level-security.rw.v7.patch:2886: trailing whitespace.
 did | cid | dlevel |      dauthor      |        dtitle         
../pgsql-v9.3-row-level-security.rw.v7.patch:2899: trailing whitespace.
 cid | did | dlevel |      dauthor      |        dtitle         |      cname      
../pgsql-v9.3-row-level-security.rw.v7.patch:2918: trailing whitespace.
 did | cid | dlevel |      dauthor      |        dtitle         
error: patch failed: src/backend/commands/copy.c:34
error: src/backend/commands/copy.c: patch does not apply

What did I do wrong here?

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-25 14:20                                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:25                                                     ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
  2012-12-03 15:36                                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-12-03 15:41                                                         ` Re: [v9.3] Row-Level Security Simon Riggs <simon@2ndQuadrant.com>
  2012-12-03 16:09                                                           ` Re: [v9.3] Row-Level Security David Fetter <david@fetter.org>
@ 2012-12-03 16:59                                                             ` Alvaro Herrera <alvherre@2ndquadrant.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Alvaro Herrera @ 2012-12-03 16:59 UTC (permalink / raw)
  To: David Fetter <david@fetter.org>; +Cc: Simon Riggs <simon@2ndQuadrant.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Robert Haas <robertmhaas@gmail.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

David Fetter escribió:
> On Mon, Dec 03, 2012 at 03:41:47PM +0000, Simon Riggs wrote:
> > On 3 December 2012 15:36, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> > > 2012/12/3 David Fetter <david@fetter.org>:
> > >> On Sun, Nov 25, 2012 at 03:20:28PM +0100, Kohei KaiGai wrote:
> > >>> > However, UPDATE / DELETE support is not perfect right now.
> > >>> > In case when we try to update / delete a table with inherited
> > >>> > children and RETURNING clause was added, is loses right
> > >>> > references to the pseudo columns, even though it works fine
> > >>> > without inherited children.
> > >>> >
> > >>> The attached patch fixed this known problem.
> > >>
> > >> This patch no longer applies to git master.  Any chance of a rebase?
> > >>
> > > OK, I'll rebese it.
> > 
> > No chunk failures, its just fuzz.
> 
> I must have done something wrong.
> 
> I downloaded the patch from the web email archives, then ran "git
> apply" on it, and got this:

git apply is much stricter than other tools; if the patch requires a
merge, it doesn't try.  Try applying it with "patch -p1 < /path/to/patch"
(this is what I always use)

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.3] Row-Level Security
  2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 16:31 ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-26 20:36   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-26 20:59     ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-06-27 05:18       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 11:21         ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-27 12:42           ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-06-27 13:07             ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-27 15:58               ` Re: [v9.3] Row-Level Security Florian Pflug <fgp@phlo.org>
  2012-06-28 15:02                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-06-28 15:29                   ` Re: [v9.3] Row-Level Security Tom Lane <tgl@sss.pgh.pa.us>
  2012-07-01 14:53                     ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-15 09:52                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-07-17 03:49                         ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-07-17 04:02                           ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-02 21:39                             ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-09-03 20:32                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-09-05 20:49                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 14:57                                   ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-08 18:33                                     ` Re: [v9.3] Row-Level Security Dean Rasheed <dean.a.rasheed@gmail.com>
  2012-10-08 19:49                                       ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-15 20:19                                         ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-10-18 18:19                                           ` Re: [v9.3] Row-Level Security Alvaro Herrera <alvherre@2ndquadrant.com>
  2012-10-22 13:33                                             ` Re: [v9.3] Row-Level Security Robert Haas <robertmhaas@gmail.com>
  2012-10-22 20:54                                               ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2012-11-15 21:07                                                 ` Re: [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2012-12-03 17:02                                                   ` Simon Riggs <simon@2ndQuadrant.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Simon Riggs @ 2012-12-03 17:02 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Alvaro Herrera <alvherre@2ndquadrant.com>; Dean Rasheed <dean.a.rasheed@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Florian Pflug <fgp@phlo.org>; pgsql-hackers

On 15 November 2012 21:07, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

> The attached patch is a revised version of row-level security
> feature.

I've got time to review this patch, so I've added myself as a CF reviewer.

Definitely looks very interesting, well done for getting it this far along.

-- 
 Simon Riggs                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* [v9.4] row level security
@ 2013-06-13 19:44 Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-06-13 19:44 UTC (permalink / raw)
  To: pgsql-hackers

The attached patch implements row-level security feature; that allows to
enforce a pre-configured security policy on reference of tables with the
row-level security policy.
It enables to isolate records to be visible from others according to access
control decision, usually done based on current user's credential.
It will make sense to ensure correctness of security for SaaS style
applications that typically share a common table for multiple users but
correctness of access control was ensured with correctness of application
itself.

Here is not functional update since the last commit fest for v9.3 except
for adjustment towards the latest master branch.

So, the explanation below might be bored for someone.

This feature enhances ALTER TABLE statement as follows:
  ALTER TABLE <tablename> SET ROW SECURITY
      FOR <command> TO (<expression>);
  ALTER TABLE <tablename> RESET ROW SECURITY
      FOR <command>;
  <command> := { ALL | SELECT | INSERT | UPDATE | DELETE }

Right now, only "ALL" is supported command, even though syntax reserves
future enhancement allows to set individual security policy for each command.
The <expression> should be an expression that returns a bool value. It can
reference any column in the target table and contain sub-query that reference
another tables.
Then, the pre-configured expression shall be added when the table is referenced.

See below, it gives "(X % 2 = 1)" as security policy, user can see the record
that has odd-number at X. The EXPLAIN output below shows this expression
was automatically attached.

postgres=> ALTER TABLE tbl SET ROW SECURITY FOR ALL TO (x % 2 = 1);
ALTER TABLE
postgres=> EXPLAIN SELECT * FROM tbl WHERE y like '%abc%';
                           QUERY PLAN
-----------------------------------------------------------------
 Subquery Scan on tbl  (cost=0.00..28.52 rows=1 width=36)
   Filter: (tbl.y ~~ '%abc%'::text)
   ->  Seq Scan on tbl tbl_1  (cost=0.00..28.45 rows=6 width=36)
         Filter: ((x % 2) = 1)
(4 rows)

An important point is, reference to a particular relation is replaced
with a sub-
query that has security policy expression and security barrier attribute.
That prevent any (non leakproof) user given condition earlier than
security poliy
itself, thus, it ensures all records user can see is satisfies the
security policy.

On writer-queries, things to do are similar. It adds security policy expression
on the scan phase of UPDATE or DELETE statement. Thus, only visible records
are updatable or deletable.

postgres=> EXPLAIN UPDATE tbl SET y = y || '_update' WHERE y like '%xyz%';
                              QUERY PLAN
-----------------------------------------------------------------------
 Update on tbl  (cost=0.00..28.53 rows=1 width=42)
   ->  Subquery Scan on tbl_1  (cost=0.00..28.53 rows=1 width=42)
         Filter: (tbl_1.y ~~ '%xyz%'::text)
         ->  Seq Scan on tbl tbl_2  (cost=0.00..28.45 rows=6 width=42)
               Filter: ((x % 2) = 1)
(5 rows)

I had a relevant presentation at PGcon last month. I think its slides
are good summary
to know brief background of the long-standing problem.
http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] pgsql-v9.4-row-level-security.v1.patch (156.9K, ../../CADyhKSUq+BHPAEdrNQXy_bFSzz2SG29vzJmDUh4p20iWje8QSg@mail.gmail.com/2-pgsql-v9.4-row-level-security.v1.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  65 ++
 doc/src/sgml/ref/alter_table.sgml          |  43 ++
 doc/src/sgml/user-manag.sgml               | 145 +++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |   8 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/objectaddress.c        |  50 ++
 src/backend/catalog/pg_rowsecurity.c       | 337 ++++++++++
 src/backend/commands/copy.c                |  90 ++-
 src/backend/commands/event_trigger.c       |   1 +
 src/backend/commands/tablecmds.c           |  27 +
 src/backend/nodes/copyfuncs.c              |   3 +
 src/backend/nodes/equalfuncs.c             |   3 +
 src/backend/nodes/nodeFuncs.c              |  12 +-
 src/backend/nodes/outfuncs.c               |   3 +
 src/backend/nodes/readfuncs.c              |   2 +
 src/backend/optimizer/plan/planner.c       |  23 +-
 src/backend/optimizer/prep/preptlist.c     |  61 +-
 src/backend/optimizer/prep/prepunion.c     |  95 ++-
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowsecurity.c   | 733 ++++++++++++++++++++++
 src/backend/parser/gram.y                  |  25 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/parser/parse_relation.c        |  19 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  13 +-
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/common.c                   |   4 +
 src/bin/pg_dump/pg_dump.c                  | 178 ++++++
 src/bin/pg_dump/pg_dump.h                  |  13 +-
 src/bin/pg_dump/pg_dump_sort.c             |   5 +
 src/bin/psql/describe.c                    |   7 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   5 +
 src/include/catalog/pg_class.h             |  24 +-
 src/include/catalog/pg_rowsecurity.h       |  76 +++
 src/include/miscadmin.h                    |   1 +
 src/include/nodes/execnodes.h              |   4 +
 src/include/nodes/nodeFuncs.h              |   1 +
 src/include/nodes/parsenodes.h             |  14 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   6 +
 src/include/optimizer/rowsecurity.h        |  27 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/parser/parsetree.h             |   5 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowsecurity.out  | 950 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowsecurity.sql       | 298 +++++++++
 55 files changed, 3432 insertions(+), 42 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index e638a8f..85952ff 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1857,6 +1862,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5094,6 +5109,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 5437626..41e5d1a 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -68,12 +68,16 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -565,6 +569,31 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -806,6 +835,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..34a699e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c4d3f3c..9b4e9f5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index 69171f8..2b2178a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2308,6 +2313,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 45a84e4..3580368 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -792,6 +792,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 215eaf5..baf6a24 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, SnapshotNow, 1, &skey);
+				tuple = systable_getnext(sscan);
+				if (!HeapTupleIsValid(tuple))
+					elog(ERROR, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..25739ff
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,337 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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,
+							   SnapshotNow, 1, &skey);
+	PG_TRY();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   SnapshotNow, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   SnapshotNow, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 31819cc..9cb8f1b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a0f97e4..9289a64 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -988,6 +988,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8294b29..62766d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2784,6 +2785,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3147,6 +3150,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3428,6 +3433,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7638,6 +7649,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..0835bda 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1933,6 +1933,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -1974,6 +1975,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(funcexpr);
@@ -2432,6 +2434,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..f688275 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -805,6 +805,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -840,6 +841,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
@@ -2222,6 +2224,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..2b265dd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..81d7774 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1920,6 +1920,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2233,6 +2234,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
@@ -2357,6 +2359,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..1a3da23 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -199,6 +199,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
@@ -1195,6 +1196,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..976b9c0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -176,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -253,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -402,6 +405,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -865,6 +881,8 @@ inheritance_planner(PlannerInfo *root)
 					newrti = list_length(subroot.parse->rtable) + 1;
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+					if (subroot.parse->sourceRelation == rti)
+						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -928,7 +946,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build list of per-relation RETURNING targetlists */
 		if (parse->returningList)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index fb67f9e..4abf434 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,48 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+				Index result_relation, List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -73,8 +113,13 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
+		Index	source_relation = (parse->sourceRelation > 0 ?
+								   parse->sourceRelation :
+								   result_relation);
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  source_relation, range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +141,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +158,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -130,7 +177,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -299,7 +346,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..0cbb035 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference artificial columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find artificial column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3f5cb19 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..d686dfa
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,733 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_artificial_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_artificial_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_artificial_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_artificial_column(PlannerInfo *root,
+						 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_artificial_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds artificial columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focues on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * Var nodes that reference the relation being replaced by row-
+		 * security sub-query has to be adjusted; to reference the sub-
+		 * query, instead of the original relation.
+		 */
+		if (context->vartrans[var->varno] != 0)
+		{
+			rte = rt_fetch(context->vartrans[var->varno], rtable);
+			if (rte->rtekind == RTE_SUBQUERY &&
+				rte->subquery->querySource == QSRC_ROW_SECURITY)
+			{
+				var->varno = var->varnoold = context->vartrans[var->varno];
+				var->varattno = lookup_artificial_column(context->root,
+														 rte, var->varattno);
+			}
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (!rte->inh)
+				return false;
+
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo  *appinfo = lfirst(cell);
+				RangeTblEntry  *child_rte;
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				if (var->varattno > InvalidAttrNumber)
+					continue;
+
+				child_rte = rt_fetch(appinfo->child_relid, rtable);
+				if (child_rte->rtekind == RTE_SUBQUERY &&
+					child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+					(void) lookup_artificial_column(context->root,
+													child_rte,
+													var->varattno);
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains artificial columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ *
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_artificial_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/* Push-down rowmark, if needed */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		/*
+		 * In case of inherited children, rti/prti of rowmark shall be
+		 * fixed up later, on inheritance_planner().
+		 */
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_artificial_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_artificial_column(root, newrte, TableOidAttributeNumber);
+	}
+
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		Relation	rel;
+		Expr	   *qual;
+		int			flags = 0;
+
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var    *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * walker on join-tree
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying artificial column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5094226..ba79767 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2156,6 +2157,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2227,6 +2246,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..14e02d4 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 7f0995f..3a48edb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1445,6 +1445,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2624,6 +2625,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..768d531 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2058,6 +2058,25 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 }
 
 /*
+ * getrelid
+ *
+ * Get OID of the relation corresponding to the given range index.
+ * Note that InvalidOid will be returned if the RTE is for neither
+ * relation nor sub-query originated from a relation
+ */
+Oid
+getrelid(Index rangeindex, List *rangetable)
+{
+	RangeTblEntry  *rte = rt_fetch(rangeindex, rangetable);
+
+	if (rte->rtekind == RTE_RELATION)
+		return rte->relid;
+	if (rte->rtekind == RTE_SUBQUERY)
+		return rte->rowsec_relid;
+	return InvalidOid;
+}
+
+/*
  * get_rte_attribute_name
  *		Get an attribute name from a RangeTblEntry
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 01875fc..abfd158 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2941,3 +2941,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 65edc1f..b36da97 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 26cae97..d05cde7 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -794,6 +795,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -846,6 +857,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -913,6 +926,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -955,6 +986,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index f114038..39bfeaf 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -933,6 +934,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1840,6 +1846,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3159,7 +3167,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4405,6 +4419,7 @@ load_relcache_init_file(bool shared)
 		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index ae52ac1..cd1bed1 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		write_msg(NULL, "reading event triggers\n");
 	getEventTriggers(fout, &numEventTriggers);
 
+	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_dump.c b/src/bin/pg_dump/pg_dump.c
index ec956ad..9a9dd6b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -249,6 +249,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2686,6 +2687,130 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	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.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_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].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)
+		return;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO (%s)", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s", 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,
@@ -4214,6 +4339,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4263,6 +4389,44 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "c.relhasrowsecurity, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "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') "
+						  "ORDER BY c.oid",
+						  username_subquery,
+						  RELKIND_SEQUENCE,
+						  RELKIND_RELATION, RELKIND_SEQUENCE,
+						  RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+						  RELKIND_FOREIGN_TABLE);
+	}
+	else if (fout->remoteVersion >= 90100)
+	{
+		/*
+		 * 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, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4337,6 +4501,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4373,6 +4538,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4409,6 +4575,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4445,6 +4612,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4481,6 +4649,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4513,6 +4682,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4540,6 +4710,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4577,6 +4748,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4624,6 +4796,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4671,6 +4844,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
 		tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
@@ -7748,6 +7922,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -14910,6 +15087,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index 5582538..31e009a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -243,6 +244,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -481,6 +483,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -572,5 +582,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index 141e713..5c5777c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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/psql/describe.c b/src/bin/psql/describe.c
index 9b6b9c2..db5c0e5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2698,6 +2698,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBuffer(&buf,
@@ -2707,6 +2711,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBuffer(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..c96e8d4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 19268fb..331a64b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+#define RowSecurityOidIndexId				3819
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+#define RowSecurityRelidIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 2225787..83d8e37 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	bool		relispopulated; /* matview currently holds query results */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -94,7 +95,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					29
+#define Natts_pg_class					30
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -118,12 +119,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		21
 #define Anum_pg_class_relhasrules		22
 #define Anum_pg_class_relhastriggers	23
-#define Anum_pg_class_relhassubclass	24
-#define Anum_pg_class_relispopulated	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
+#define Anum_pg_class_relhasrowsecurity	24
+#define Anum_pg_class_relhassubclass	25
+#define Anum_pg_class_relispopulated	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
@@ -138,13 +140,13 @@ typedef FormData_pg_class *Form_pg_class;
  * 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 0 f f p r 30 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 29 0 t f f f f t 3 1 _null_ _null_ ));
+DATA(insert OID = 1259 (  pg_class		PGNSP 83 0 PGUID 0 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t 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 0000000..a068b35
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,76 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	3838
+
+CATALOG(pg_rowsecurity,3838)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index be3add9..d266aa9 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -278,6 +278,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..f49cbf9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -306,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -325,6 +327,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index d4901ca..cb30110 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6723647..fbf4c80 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -112,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -719,6 +722,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
@@ -1244,6 +1252,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..cc94421 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index c0a636b..ed01b65 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -1416,6 +1418,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..a3d5be1 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY,			/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index 9608cc6..b4e9df4 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -32,14 +32,11 @@
 	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
 
 /*
- *		getrelid
- *
  *		Given the range index of a relation, return the corresponding
  *		relation OID.  Note that InvalidOid will be returned if the
  *		RTE is for a non-relation-type RTE.
  */
-#define getrelid(rangeindex,rangetable) \
-	(rt_fetch(rangeindex, rangetable)->relid)
+extern Oid	getrelid(Index rangeindex, List *rangetable);
 
 /*
  * Given an RTE and an attribute number, return the appropriate
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 5983315..840807a 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 72f8491..35819fa 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 58cc3f7..5e8441c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..04591df
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,950 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 432d39a..3b9123d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowsecurity          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2af28b1..3a36bce 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview
+test: privileges rowsecurity security_label collate matview
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index d6eaa7a..9f52f6a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -93,6 +93,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-07-08 07:57 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-07-08 07:57 UTC (permalink / raw)
  To: pgsql-hackers

I was pointed out that the previous patch was not applied cleanly because of
enhancement on pg_class system catalog, and related pg_dump portion was
getting broken.

The attached patch fixes this matters. Please reference this patch instead.
Thanks,

2013/6/13 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> The attached patch implements row-level security feature; that allows to
> enforce a pre-configured security policy on reference of tables with the
> row-level security policy.
> It enables to isolate records to be visible from others according to access
> control decision, usually done based on current user's credential.
> It will make sense to ensure correctness of security for SaaS style
> applications that typically share a common table for multiple users but
> correctness of access control was ensured with correctness of application
> itself.
>
> Here is not functional update since the last commit fest for v9.3 except
> for adjustment towards the latest master branch.
>
> So, the explanation below might be bored for someone.
>
> This feature enhances ALTER TABLE statement as follows:
>   ALTER TABLE <tablename> SET ROW SECURITY
>       FOR <command> TO (<expression>);
>   ALTER TABLE <tablename> RESET ROW SECURITY
>       FOR <command>;
>   <command> := { ALL | SELECT | INSERT | UPDATE | DELETE }
>
> Right now, only "ALL" is supported command, even though syntax reserves
> future enhancement allows to set individual security policy for each command.
> The <expression> should be an expression that returns a bool value. It can
> reference any column in the target table and contain sub-query that reference
> another tables.
> Then, the pre-configured expression shall be added when the table is referenced.
>
> See below, it gives "(X % 2 = 1)" as security policy, user can see the record
> that has odd-number at X. The EXPLAIN output below shows this expression
> was automatically attached.
>
> postgres=> ALTER TABLE tbl SET ROW SECURITY FOR ALL TO (x % 2 = 1);
> ALTER TABLE
> postgres=> EXPLAIN SELECT * FROM tbl WHERE y like '%abc%';
>                            QUERY PLAN
> -----------------------------------------------------------------
>  Subquery Scan on tbl  (cost=0.00..28.52 rows=1 width=36)
>    Filter: (tbl.y ~~ '%abc%'::text)
>    ->  Seq Scan on tbl tbl_1  (cost=0.00..28.45 rows=6 width=36)
>          Filter: ((x % 2) = 1)
> (4 rows)
>
> An important point is, reference to a particular relation is replaced
> with a sub-
> query that has security policy expression and security barrier attribute.
> That prevent any (non leakproof) user given condition earlier than
> security poliy
> itself, thus, it ensures all records user can see is satisfies the
> security policy.
>
> On writer-queries, things to do are similar. It adds security policy expression
> on the scan phase of UPDATE or DELETE statement. Thus, only visible records
> are updatable or deletable.
>
> postgres=> EXPLAIN UPDATE tbl SET y = y || '_update' WHERE y like '%xyz%';
>                               QUERY PLAN
> -----------------------------------------------------------------------
>  Update on tbl  (cost=0.00..28.53 rows=1 width=42)
>    ->  Subquery Scan on tbl_1  (cost=0.00..28.53 rows=1 width=42)
>          Filter: (tbl_1.y ~~ '%xyz%'::text)
>          ->  Seq Scan on tbl tbl_2  (cost=0.00..28.45 rows=6 width=42)
>                Filter: ((x % 2) = 1)
> (5 rows)
>
> I had a relevant presentation at PGcon last month. I think its slides
> are good summary
> to know brief background of the long-standing problem.
> http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf
>
> Thanks,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] pgsql-v9.4-row-level-security.v3.patch (159.6K, ../../CADyhKSVNMMvmPCQkpaVb96T4F3Eu+uREnak4_5_5RLOuSeJLQg@mail.gmail.com/2-pgsql-v9.4-row-level-security.v3.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  65 ++
 doc/src/sgml/ref/alter_table.sgml          |  43 ++
 doc/src/sgml/user-manag.sgml               | 145 +++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |   8 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/objectaddress.c        |  50 ++
 src/backend/catalog/pg_rowsecurity.c       | 337 ++++++++++
 src/backend/commands/copy.c                |  90 ++-
 src/backend/commands/event_trigger.c       |   1 +
 src/backend/commands/tablecmds.c           |  27 +
 src/backend/nodes/copyfuncs.c              |   3 +
 src/backend/nodes/equalfuncs.c             |   3 +
 src/backend/nodes/nodeFuncs.c              |  12 +-
 src/backend/nodes/outfuncs.c               |   3 +
 src/backend/nodes/readfuncs.c              |   2 +
 src/backend/optimizer/plan/planner.c       |  23 +-
 src/backend/optimizer/prep/preptlist.c     |  61 +-
 src/backend/optimizer/prep/prepunion.c     |  95 ++-
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowsecurity.c   | 733 ++++++++++++++++++++++
 src/backend/parser/gram.y                  |  25 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/parser/parse_relation.c        |  19 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  13 +-
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/common.c                   |   4 +
 src/bin/pg_dump/pg_backup_archiver.c       |   1 +
 src/bin/pg_dump/pg_dump.c                  | 223 ++++++-
 src/bin/pg_dump/pg_dump.h                  |  13 +-
 src/bin/pg_dump/pg_dump_sort.c             |   5 +
 src/bin/psql/describe.c                    |   7 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   5 +
 src/include/catalog/pg_class.h             |  24 +-
 src/include/catalog/pg_rowsecurity.h       |  76 +++
 src/include/miscadmin.h                    |   1 +
 src/include/nodes/execnodes.h              |   4 +
 src/include/nodes/nodeFuncs.h              |   1 +
 src/include/nodes/parsenodes.h             |  14 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   6 +
 src/include/optimizer/rowsecurity.h        |  27 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/parser/parsetree.h             |   5 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowsecurity.out  | 950 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowsecurity.sql       | 298 +++++++++
 56 files changed, 3477 insertions(+), 43 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6715782..6f3c8fc 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1848,6 +1853,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5112,6 +5127,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2609d4a..b380852 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,12 +69,16 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -580,6 +584,31 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -821,6 +850,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..34a699e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c4d3f3c..9b4e9f5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index fe17c96..d13b2e1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2308,6 +2313,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 64ca312..7eb7a17 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -791,6 +791,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 4d22f3a..b4bc9d8 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, SnapshotNow, 1, &skey);
+				tuple = systable_getnext(sscan);
+				if (!HeapTupleIsValid(tuple))
+					elog(ERROR, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..25739ff
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,337 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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,
+							   SnapshotNow, 1, &skey);
+	PG_TRY();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   SnapshotNow, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   SnapshotNow, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 31819cc..9cb8f1b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 328e2a8..0374629 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -992,6 +992,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f56ef28..770bcc2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2787,6 +2788,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3155,6 +3158,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3440,6 +3445,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7788,6 +7799,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..0835bda 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1933,6 +1933,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -1974,6 +1975,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(funcexpr);
@@ -2432,6 +2434,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..f688275 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -805,6 +805,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -840,6 +841,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
@@ -2222,6 +2224,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..2b265dd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..81d7774 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1920,6 +1920,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2233,6 +2234,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
@@ -2357,6 +2359,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..1a3da23 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -199,6 +199,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
@@ -1195,6 +1196,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..976b9c0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -176,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -253,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -402,6 +405,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -865,6 +881,8 @@ inheritance_planner(PlannerInfo *root)
 					newrti = list_length(subroot.parse->rtable) + 1;
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+					if (subroot.parse->sourceRelation == rti)
+						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -928,7 +946,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build list of per-relation RETURNING targetlists */
 		if (parse->returningList)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index fb67f9e..4abf434 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,48 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+				Index result_relation, List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -73,8 +113,13 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
+		Index	source_relation = (parse->sourceRelation > 0 ?
+								   parse->sourceRelation :
+								   result_relation);
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  source_relation, range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +141,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +158,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -130,7 +177,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -299,7 +346,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..0cbb035 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference artificial columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find artificial column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3f5cb19 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..d686dfa
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,733 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_artificial_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_artificial_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_artificial_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_artificial_column(PlannerInfo *root,
+						 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_artificial_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds artificial columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focues on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * Var nodes that reference the relation being replaced by row-
+		 * security sub-query has to be adjusted; to reference the sub-
+		 * query, instead of the original relation.
+		 */
+		if (context->vartrans[var->varno] != 0)
+		{
+			rte = rt_fetch(context->vartrans[var->varno], rtable);
+			if (rte->rtekind == RTE_SUBQUERY &&
+				rte->subquery->querySource == QSRC_ROW_SECURITY)
+			{
+				var->varno = var->varnoold = context->vartrans[var->varno];
+				var->varattno = lookup_artificial_column(context->root,
+														 rte, var->varattno);
+			}
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (!rte->inh)
+				return false;
+
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo  *appinfo = lfirst(cell);
+				RangeTblEntry  *child_rte;
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				if (var->varattno > InvalidAttrNumber)
+					continue;
+
+				child_rte = rt_fetch(appinfo->child_relid, rtable);
+				if (child_rte->rtekind == RTE_SUBQUERY &&
+					child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+					(void) lookup_artificial_column(context->root,
+													child_rte,
+													var->varattno);
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains artificial columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ *
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_artificial_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/* Push-down rowmark, if needed */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		/*
+		 * In case of inherited children, rti/prti of rowmark shall be
+		 * fixed up later, on inheritance_planner().
+		 */
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_artificial_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_artificial_column(root, newrte, TableOidAttributeNumber);
+	}
+
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		Relation	rel;
+		Expr	   *qual;
+		int			flags = 0;
+
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var    *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * walker on join-tree
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying artificial column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..af2ae94 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2173,6 +2174,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2244,6 +2263,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..14e02d4 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 06f6512..0334ecb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1445,6 +1445,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2624,6 +2625,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..768d531 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2058,6 +2058,25 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 }
 
 /*
+ * getrelid
+ *
+ * Get OID of the relation corresponding to the given range index.
+ * Note that InvalidOid will be returned if the RTE is for neither
+ * relation nor sub-query originated from a relation
+ */
+Oid
+getrelid(Index rangeindex, List *rangetable)
+{
+	RangeTblEntry  *rte = rt_fetch(rangeindex, rangetable);
+
+	if (rte->rtekind == RTE_RELATION)
+		return rte->relid;
+	if (rte->rtekind == RTE_SUBQUERY)
+		return rte->rowsec_relid;
+	return InvalidOid;
+}
+
+/*
  * get_rte_attribute_name
  *		Get an attribute name from a RangeTblEntry
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d909de3..1c8c984 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3002,3 +3002,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 65edc1f..b36da97 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 26cae97..d05cde7 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -794,6 +795,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -846,6 +857,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -913,6 +926,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -955,6 +986,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66fb63b..828c10e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -935,6 +936,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1842,6 +1848,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3161,7 +3169,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4407,6 +4421,7 @@ load_relcache_init_file(bool shared)
 		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 247ad92..9603ef4 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		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
index cd7669b..e9ca251 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3148,6 +3148,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 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
index f40961f..152ca03 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -249,6 +249,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2686,6 +2687,134 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		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.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_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].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)
+		return;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO (%s)", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s", 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,
@@ -4214,6 +4343,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4251,7 +4381,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90300)
+	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, "
+						  "c.relhasrowsecurity, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "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 >= 90300)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -4263,6 +4431,45 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "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 >= 90100)
+	{
+		/*
+		 * 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, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4337,6 +4544,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4373,6 +4581,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4409,6 +4618,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4445,6 +4655,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4481,6 +4692,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4513,6 +4725,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4540,6 +4753,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4577,6 +4791,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4624,6 +4839,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4671,6 +4887,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
 		tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
@@ -7748,6 +7965,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -14922,6 +15142,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index 5582538..31e009a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -243,6 +244,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -481,6 +483,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -572,5 +582,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index 141e713..5c5777c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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/psql/describe.c b/src/bin/psql/describe.c
index 9b6b9c2..db5c0e5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2698,6 +2698,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBuffer(&buf,
@@ -2707,6 +2711,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBuffer(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..c96e8d4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 19268fb..331a64b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+#define RowSecurityOidIndexId				3819
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+#define RowSecurityRelidIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 49c4f6f..6a16819 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -64,6 +64,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	bool		relispopulated; /* matview currently holds query results */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					28
+#define Natts_pg_class					29
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -116,12 +117,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		20
 #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_relfrozenxid		25
-#define Anum_pg_class_relminmxid		26
-#define Anum_pg_class_relacl			27
-#define Anum_pg_class_reloptions		28
+#define Anum_pg_class_relhasrowsecurity	23
+#define Anum_pg_class_relhassubclass	24
+#define Anum_pg_class_relispopulated	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
@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
  * 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 28 0 t f f f f t 3 1 _null_ _null_ ));
+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 f t 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 0000000..a068b35
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,76 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	3838
+
+CATALOG(pg_rowsecurity,3838)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 48985b3..c182d2d 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -279,6 +279,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..f49cbf9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -306,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -325,6 +327,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index d4901ca..cb30110 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..2b81e27 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -112,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -724,6 +727,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
@@ -1250,6 +1258,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..cc94421 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index c0a636b..ed01b65 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -1416,6 +1418,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..a3d5be1 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY,			/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index 9608cc6..b4e9df4 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -32,14 +32,11 @@
 	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
 
 /*
- *		getrelid
- *
  *		Given the range index of a relation, return the corresponding
  *		relation OID.  Note that InvalidOid will be returned if the
  *		RTE is for a non-relation-type RTE.
  */
-#define getrelid(rangeindex,rangetable) \
-	(rt_fetch(rangeindex, rangetable)->relid)
+extern Oid	getrelid(Index rangeindex, List *rangetable);
 
 /*
  * Given an RTE and an attribute number, return the appropriate
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 1831de4..ef54c57 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 72f8491..35819fa 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 58cc3f7..5e8441c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..04591df
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,950 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 432d39a..3b9123d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowsecurity          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3e6b306..f2d75f5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview
+test: privileges rowsecurity security_label collate matview
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 3ad289f..d44e89b 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -93,6 +93,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-07-08 10:19   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-07-08 10:19 UTC (permalink / raw)
  To: pgsql-hackers

Sorry, this included a garbage chunk when I made a patch.

The attached one is corrected revision. Please see this patch instead.
Thanks,

2013/7/8 Kohei KaiGai <kaigai@kaigai.gr.jp>:
> I was pointed out that the previous patch was not applied cleanly because of
> enhancement on pg_class system catalog, and related pg_dump portion was
> getting broken.
>
> The attached patch fixes this matters. Please reference this patch instead.
> Thanks,
>
> 2013/6/13 Kohei KaiGai <kaigai@kaigai.gr.jp>:
>> The attached patch implements row-level security feature; that allows to
>> enforce a pre-configured security policy on reference of tables with the
>> row-level security policy.
>> It enables to isolate records to be visible from others according to access
>> control decision, usually done based on current user's credential.
>> It will make sense to ensure correctness of security for SaaS style
>> applications that typically share a common table for multiple users but
>> correctness of access control was ensured with correctness of application
>> itself.
>>
>> Here is not functional update since the last commit fest for v9.3 except
>> for adjustment towards the latest master branch.
>>
>> So, the explanation below might be bored for someone.
>>
>> This feature enhances ALTER TABLE statement as follows:
>>   ALTER TABLE <tablename> SET ROW SECURITY
>>       FOR <command> TO (<expression>);
>>   ALTER TABLE <tablename> RESET ROW SECURITY
>>       FOR <command>;
>>   <command> := { ALL | SELECT | INSERT | UPDATE | DELETE }
>>
>> Right now, only "ALL" is supported command, even though syntax reserves
>> future enhancement allows to set individual security policy for each command.
>> The <expression> should be an expression that returns a bool value. It can
>> reference any column in the target table and contain sub-query that reference
>> another tables.
>> Then, the pre-configured expression shall be added when the table is referenced.
>>
>> See below, it gives "(X % 2 = 1)" as security policy, user can see the record
>> that has odd-number at X. The EXPLAIN output below shows this expression
>> was automatically attached.
>>
>> postgres=> ALTER TABLE tbl SET ROW SECURITY FOR ALL TO (x % 2 = 1);
>> ALTER TABLE
>> postgres=> EXPLAIN SELECT * FROM tbl WHERE y like '%abc%';
>>                            QUERY PLAN
>> -----------------------------------------------------------------
>>  Subquery Scan on tbl  (cost=0.00..28.52 rows=1 width=36)
>>    Filter: (tbl.y ~~ '%abc%'::text)
>>    ->  Seq Scan on tbl tbl_1  (cost=0.00..28.45 rows=6 width=36)
>>          Filter: ((x % 2) = 1)
>> (4 rows)
>>
>> An important point is, reference to a particular relation is replaced
>> with a sub-
>> query that has security policy expression and security barrier attribute.
>> That prevent any (non leakproof) user given condition earlier than
>> security poliy
>> itself, thus, it ensures all records user can see is satisfies the
>> security policy.
>>
>> On writer-queries, things to do are similar. It adds security policy expression
>> on the scan phase of UPDATE or DELETE statement. Thus, only visible records
>> are updatable or deletable.
>>
>> postgres=> EXPLAIN UPDATE tbl SET y = y || '_update' WHERE y like '%xyz%';
>>                               QUERY PLAN
>> -----------------------------------------------------------------------
>>  Update on tbl  (cost=0.00..28.53 rows=1 width=42)
>>    ->  Subquery Scan on tbl_1  (cost=0.00..28.53 rows=1 width=42)
>>          Filter: (tbl_1.y ~~ '%xyz%'::text)
>>          ->  Seq Scan on tbl tbl_2  (cost=0.00..28.45 rows=6 width=42)
>>                Filter: ((x % 2) = 1)
>> (5 rows)
>>
>> I had a relevant presentation at PGcon last month. I think its slides
>> are good summary
>> to know brief background of the long-standing problem.
>> http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf
>>
>> Thanks,
>> --
>> KaiGai Kohei <kaigai@kaigai.gr.jp>
>
>
>
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] pgsql-v9.4-row-level-security.v3.patch (158.3K, ../../CADyhKSXFzQBXQoNwiKdN9xxoVw7Zqz9gmF64Sry57QKduSFwSQ@mail.gmail.com/2-pgsql-v9.4-row-level-security.v3.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  65 ++
 doc/src/sgml/ref/alter_table.sgml          |  43 ++
 doc/src/sgml/user-manag.sgml               | 145 +++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |   8 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/objectaddress.c        |  50 ++
 src/backend/catalog/pg_rowsecurity.c       | 337 ++++++++++
 src/backend/commands/copy.c                |  90 ++-
 src/backend/commands/event_trigger.c       |   1 +
 src/backend/commands/tablecmds.c           |  27 +
 src/backend/nodes/copyfuncs.c              |   3 +
 src/backend/nodes/equalfuncs.c             |   3 +
 src/backend/nodes/nodeFuncs.c              |  12 +-
 src/backend/nodes/outfuncs.c               |   3 +
 src/backend/nodes/readfuncs.c              |   2 +
 src/backend/optimizer/plan/planner.c       |  23 +-
 src/backend/optimizer/prep/preptlist.c     |  61 +-
 src/backend/optimizer/prep/prepunion.c     |  95 ++-
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowsecurity.c   | 733 ++++++++++++++++++++++
 src/backend/parser/gram.y                  |  25 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/parser/parse_relation.c        |  19 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  13 +-
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/common.c                   |   4 +
 src/bin/pg_dump/pg_backup_archiver.c       |   1 +
 src/bin/pg_dump/pg_dump.c                  | 186 +++++-
 src/bin/pg_dump/pg_dump.h                  |  13 +-
 src/bin/pg_dump/pg_dump_sort.c             |   5 +
 src/bin/psql/describe.c                    |   7 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   5 +
 src/include/catalog/pg_class.h             |  24 +-
 src/include/catalog/pg_rowsecurity.h       |  76 +++
 src/include/miscadmin.h                    |   1 +
 src/include/nodes/execnodes.h              |   4 +
 src/include/nodes/nodeFuncs.h              |   1 +
 src/include/nodes/parsenodes.h             |  14 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   6 +
 src/include/optimizer/rowsecurity.h        |  27 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/parser/parsetree.h             |   5 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowsecurity.out  | 950 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowsecurity.sql       | 298 +++++++++
 56 files changed, 3440 insertions(+), 43 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6715782..6f3c8fc 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1848,6 +1853,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5112,6 +5127,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2609d4a..b380852 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,12 +69,16 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -580,6 +584,31 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -821,6 +850,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..34a699e 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.3 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c4d3f3c..9b4e9f5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index fe17c96..d13b2e1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2308,6 +2313,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 64ca312..7eb7a17 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -791,6 +791,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 4d22f3a..b4bc9d8 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, SnapshotNow, 1, &skey);
+				tuple = systable_getnext(sscan);
+				if (!HeapTupleIsValid(tuple))
+					elog(ERROR, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..25739ff
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,337 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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,
+							   SnapshotNow, 1, &skey);
+	PG_TRY();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   SnapshotNow, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   SnapshotNow, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 31819cc..9cb8f1b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 328e2a8..0374629 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -992,6 +992,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f56ef28..770bcc2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2787,6 +2788,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3155,6 +3158,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3440,6 +3445,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7788,6 +7799,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63..0835bda 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1933,6 +1933,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -1974,6 +1975,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(funcexpr);
@@ -2432,6 +2434,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595..f688275 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -805,6 +805,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -840,6 +841,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
@@ -2222,6 +2224,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 42d6621..2b265dd 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1869,8 +1869,11 @@ query_tree_walker(Query *query,
 
 	if (walker((Node *) query->targetList, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2583,7 +2586,10 @@ query_tree_mutator(Query *query,
 	}
 
 	MUTATE(query->targetList, query->targetList, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f4..81d7774 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1920,6 +1920,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2233,6 +2234,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
@@ -2357,6 +2359,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3a16e9d..1a3da23 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -199,6 +199,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
@@ -1195,6 +1196,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d80c264..976b9c0 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -176,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -253,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -402,6 +405,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -865,6 +881,8 @@ inheritance_planner(PlannerInfo *root)
 					newrti = list_length(subroot.parse->rtable) + 1;
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+					if (subroot.parse->sourceRelation == rti)
+						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -928,7 +946,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build list of per-relation RETURNING targetlists */
 		if (parse->returningList)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index fb67f9e..4abf434 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,48 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+				Index result_relation, List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -73,8 +113,13 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
+		Index	source_relation = (parse->sourceRelation > 0 ?
+								   parse->sourceRelation :
+								   result_relation);
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  source_relation, range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +141,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +158,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -130,7 +177,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			/* Not a table, so we need the whole row as a junk var */
 			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
 								  rc->rti,
-								  0,
+								  lookup_varattno(0, rc->rti, range_table),
 								  false);
 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
 			tle = makeTargetEntry((Expr *) var,
@@ -299,7 +346,9 @@ expand_targetlist(List *tlist, int command_type,
 					if (!att_tup->attisdropped)
 					{
 						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+													lookup_varattno(attrno,
+														result_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..0cbb035 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference artificial columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find artificial column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3f5cb19 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..d686dfa
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,733 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_artificial_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_artificial_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_artificial_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_artificial_column(PlannerInfo *root,
+						 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_artificial_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds artificial columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focues on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		/*
+		 * Var nodes that reference the relation being replaced by row-
+		 * security sub-query has to be adjusted; to reference the sub-
+		 * query, instead of the original relation.
+		 */
+		if (context->vartrans[var->varno] != 0)
+		{
+			rte = rt_fetch(context->vartrans[var->varno], rtable);
+			if (rte->rtekind == RTE_SUBQUERY &&
+				rte->subquery->querySource == QSRC_ROW_SECURITY)
+			{
+				var->varno = var->varnoold = context->vartrans[var->varno];
+				var->varattno = lookup_artificial_column(context->root,
+														 rte, var->varattno);
+			}
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (!rte->inh)
+				return false;
+
+			foreach (cell, context->root->append_rel_list)
+			{
+				AppendRelInfo  *appinfo = lfirst(cell);
+				RangeTblEntry  *child_rte;
+
+				if (appinfo->parent_relid != var->varno)
+					continue;
+
+				if (var->varattno > InvalidAttrNumber)
+					continue;
+
+				child_rte = rt_fetch(appinfo->child_relid, rtable);
+				if (child_rte->rtekind == RTE_SUBQUERY &&
+					child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+					(void) lookup_artificial_column(context->root,
+													child_rte,
+													var->varattno);
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains artificial columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ *
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_artificial_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/* Push-down rowmark, if needed */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		/*
+		 * In case of inherited children, rti/prti of rowmark shall be
+		 * fixed up later, on inheritance_planner().
+		 */
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_artificial_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_artificial_column(root, newrte, TableOidAttributeNumber);
+	}
+
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		Relation	rel;
+		Expr	   *qual;
+		int			flags = 0;
+
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var    *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * walker on join-tree
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying artificial column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f67ef0c..af2ae94 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2173,6 +2174,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2244,6 +2263,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 7380618..14e02d4 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -269,6 +269,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -537,6 +540,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 06f6512..0334ecb 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1445,6 +1445,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2624,6 +2625,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index a9254c8..768d531 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2058,6 +2058,25 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 }
 
 /*
+ * getrelid
+ *
+ * Get OID of the relation corresponding to the given range index.
+ * Note that InvalidOid will be returned if the RTE is for neither
+ * relation nor sub-query originated from a relation
+ */
+Oid
+getrelid(Index rangeindex, List *rangetable)
+{
+	RangeTblEntry  *rte = rt_fetch(rangeindex, rangetable);
+
+	if (rte->rtekind == RTE_RELATION)
+		return rte->relid;
+	if (rte->rtekind == RTE_SUBQUERY)
+		return rte->rowsec_relid;
+	return InvalidOid;
+}
+
+/*
  * get_rte_attribute_name
  *		Get an attribute name from a RangeTblEntry
  *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index d909de3..1c8c984 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3002,3 +3002,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 65edc1f..b36da97 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 26cae97..d05cde7 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/planmain.h"
 #include "optimizer/prep.h"
@@ -794,6 +795,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -846,6 +857,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -913,6 +926,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -955,6 +986,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66fb63b..828c10e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -935,6 +936,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1842,6 +1848,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3161,7 +3169,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4407,6 +4421,7 @@ load_relcache_init_file(bool shared)
 		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 247ad92..9603ef4 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		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
index cd7669b..e9ca251 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3148,6 +3148,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 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
index f40961f..fb15f6f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -249,6 +249,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2686,6 +2687,134 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		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.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_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].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;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s;\n", 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,
@@ -4214,6 +4343,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4251,7 +4381,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90300)
+	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, "
+						  "c.relhasrowsecurity, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "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 >= 90300)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -4263,6 +4431,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4300,6 +4469,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
@@ -4337,6 +4507,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4373,6 +4544,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4409,6 +4581,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4445,6 +4618,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4481,6 +4655,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4513,6 +4688,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4540,6 +4716,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4577,6 +4754,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4624,6 +4802,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4671,6 +4850,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
 		tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
@@ -7748,6 +7928,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -14922,6 +15105,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index 5582538..31e009a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -243,6 +244,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -481,6 +483,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -572,5 +582,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index 141e713..5c5777c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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/psql/describe.c b/src/bin/psql/describe.c
index 9b6b9c2..db5c0e5 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2698,6 +2698,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBuffer(&buf,
@@ -2707,6 +2711,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBuffer(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..c96e8d4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 19268fb..331a64b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -311,6 +311,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+#define RowSecurityOidIndexId				3819
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+#define RowSecurityRelidIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 49c4f6f..6a16819 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -64,6 +64,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	bool		relispopulated; /* matview currently holds query results */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					28
+#define Natts_pg_class					29
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -116,12 +117,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		20
 #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_relfrozenxid		25
-#define Anum_pg_class_relminmxid		26
-#define Anum_pg_class_relacl			27
-#define Anum_pg_class_reloptions		28
+#define Anum_pg_class_relhasrowsecurity	23
+#define Anum_pg_class_relhassubclass	24
+#define Anum_pg_class_relispopulated	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
@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
  * 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 28 0 t f f f f t 3 1 _null_ _null_ ));
+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 f t 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 0000000..a068b35
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,76 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	3838
+
+CATALOG(pg_rowsecurity,3838)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 48985b3..c182d2d 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -279,6 +279,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/execnodes.h b/src/include/nodes/execnodes.h
index 4f77016..f49cbf9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -306,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -325,6 +327,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index d4901ca..cb30110 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index de22dff..2b81e27 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -112,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -724,6 +727,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
@@ -1250,6 +1258,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 841701e..cc94421 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index c0a636b..ed01b65 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -1416,6 +1418,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 49ca764..a3d5be1 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -62,7 +62,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY,			/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index 9608cc6..b4e9df4 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -32,14 +32,11 @@
 	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
 
 /*
- *		getrelid
- *
  *		Given the range index of a relation, return the corresponding
  *		relation OID.  Note that InvalidOid will be returned if the
  *		RTE is for a non-relation-type RTE.
  */
-#define getrelid(rangeindex,rangetable) \
-	(rt_fetch(rangeindex, rangetable)->relid)
+extern Oid	getrelid(Index rangeindex, List *rangetable);
 
 /*
  * Given an RTE and an attribute number, return the appropriate
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index 1831de4..ef54c57 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 72f8491..35819fa 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 58cc3f7..5e8441c 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..04591df
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,950 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 432d39a..3b9123d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowsecurity          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 3e6b306..f2d75f5 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview
+test: privileges rowsecurity security_label collate matview
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 3ad289f..d44e89b 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -93,6 +93,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-07-08 18:58     ` Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Mike Blackwell @ 2013-07-08 18:58 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: pgsql-hackers

With the current HEAD and v3 patch I'm seeing the following error with
"make check".

------
============== creating temporary installation        ==============
============== initializing database system           ==============

pg_regress: initdb failed
Examine /usr/local/src/postgres.patched.v3/src/test/regress/log/initdb.log
for the reason.
Command was:
"/usr/local/src/postgres.patched.v3/src/test/regress/./tmp_check/install//usr/local/pgsql/bin/initdb"
-D "/usr/local/src/postgres.patched.v3/src/test/regress/./tmp_check/data"
-L
"/usr/local/src/postgres.patched.v3/src/test/regress/./tmp_check/install//usr/local/pgsql/share"
--noclean --nosync >
"/usr/local/src/postgres.patched.v3/src/test/regress/log/initdb.log" 2>&1
make[1]: *** [check] Error 2
make[1]: Leaving directory
`/usr/local/src/postgres.patched.v3/src/test/regress'
make: *** [check] Error 2

__________________________________________________________________________________
*Mike Blackwell | Technical Analyst, Distribution Services/Rollout
Management | RR Donnelley*
1750 Wallace Ave | St Charles, IL 60174-3401
Office: 630.313.7818
Mike.Blackwell@rrd.com
http://www.rrdonnelley.com


 <http://www.rrdonnelley.com/;
* <Mike.Blackwell@rrd.com>*

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
@ 2013-07-18 23:57       ` Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Karol Trzcionka @ 2013-07-18 23:57 UTC (permalink / raw)
  To: Mike Blackwell <mike.blackwell@rrd.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

Current head 4cbe3ac3e86790d05c569de4585e5075a62a9b41 -> patch applies
correct (only change needed in parallel_schedule).
However it fails on own regression tests (other tests pass).
Regards,
Karol


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
@ 2013-07-19 02:47         ` Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  0 siblings, 1 reply; 165+ messages in thread

From: Greg Smith @ 2013-07-19 02:47 UTC (permalink / raw)
  To: Karol Trzcionka <karlikt@gmail.com>; +Cc: Mike Blackwell <mike.blackwell@rrd.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

On 7/18/13 7:57 PM, Karol Trzcionka wrote:
> Current head 4cbe3ac3e86790d05c569de4585e5075a62a9b41 -> patch applies
> correct (only change needed in parallel_schedule).
> However it fails on own regression tests (other tests pass).

I got a rejected hunk in src/backend/nodes/nodeFuncs.c as well as that 
parallel_schedule issue.  Maybe you didn't get the nodeFuncs change but 
didn't notice that?  That might explain why the tests didn't work for 
you either.

Attached is an updated patch where I tried to only fix the two small 
hunks of bit rot.  I get "All 140 tests passed" here, on a Mac no less.

I did a brief code scan through the patch just to get a feel for how the 
feature is put together, and what you'd need to know for a deeper 
review.  (I'm trying to get customer time approved to work on this a lot 
more)  The code was easier to follow than I expected.  The way it 
completely avoids even getting into the security label integration yet 
seems like a successful design partitioning.  This isn't nearly as scary 
as the SEPostgres patches.  There are some useful looking utility 
functions that dump information about what's going on too.

The bulk of the complexity is how the feature modifies query nodes to 
restrict what rows come through them.  Some familiarity with that part 
of the code is what you'd need to take on reviewing this in detail. 
That and a week of time to spend trudging through it.  If anyone is 
looking for an educational challenge on query execution, marching 
through all of these changes to validate they work as expected would do 
that.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index 6715782..6f3c8fc
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 234,239 ****
--- 234,244 ----
       </row>
  
       <row>
+       <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+       <entry>row-level security policy of relation</entry>
+      </row>
+ 
+      <row>
        <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
        <entry>security labels on database objects</entry>
       </row>
***************
*** 1848,1853 ****
--- 1853,1868 ----
       </row>
  
       <row>
+       <entry><structfield>relhasrowsecurity</structfield></entry>
+       <entry><type>bool</type></entry>
+       <entry></entry>
+       <entry>
+        True if table has row-security policy; see
+        <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>relhassubclass</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
***************
*** 5112,5117 ****
--- 5127,5182 ----
  
   </sect1>
  
+  <sect1 id="catalog-pg-rowsecurity">
+   <title><structname>pg_rowlevelsec</structname></title>
+ 
+   <indexterm zone="catalog-pg-rowsecurity">
+    <primary>pg_rowsecurity</primary>
+   </indexterm>
+   <para>
+    The catalog <structname>pg_rowsecurity</structname> stores expression
+    tree of row-security policy to be performed on a particular relation.
+   </para>
+   <table>
+    <title><structname>pg_rowsecurity</structname> Columns</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>References</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>rsecrelid</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+       <entry>The table this row-security is for</entry>
+      </row>
+      <row>
+       <entry><structfield>rseccmd</structfield></entry>
+       <entry><type>char</type></entry>
+       <entry></entry>
+       <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+      </row>
+      <row>
+       <entry><structfield>rsecqual</structfield></entry>
+       <entry><type>pg_node_tree</type></entry>
+       <entry></entry>
+       <entry>An expression tree to be performed as rowl-security policy</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+   <note>
+    <para>
+     <literal>pg_class.relhasrowlevelsec</literal>
+     must be true if a table has row-level security policy in this catalog.
+    </para>
+   </note>
+  </sect1>
  
   <sect1 id="catalog-pg-seclabel">
    <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
new file mode 100644
index 2609d4a..b380852
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 69,80 ****
--- 69,84 ----
      NOT OF
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+     SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+     RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
  
  <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
  
      [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
      { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
      [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ <phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+     { ALL | SELECT | INSERT | UPDATE | DELETE }
  </synopsis>
   </refsynopsisdiv>
  
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 580,585 ****
--- 584,614 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+     <listitem>
+      <para>
+       This form set row-level security policy of the table.
+       Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+       as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+       clause, although mechanism guarantees to evaluate this condition earlier
+       than any other user given condition.
+       <literal>ALL</> is the only supported command type right now.
+       See also <xref linkend="ROW-SECURITY">.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form reset row-level security policy of the table, if exists.
+       <literal>ALL</> is the only supported command type right now.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>RENAME</literal></term>
      <listitem>
       <para>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 821,826 ****
--- 850,869 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">condition</replaceable></term>
+       <listitem>
+        <para>
+         An expression that returns a value of type boolean. Expect for a case
+         when queries are executed with superuser privilege, only rows for which
+         this expression returns true will be fetched, updated or deleted.
+         This expression can reference columns of the relation being configured.
+         Sub-queries can be contained within expression tree, unless referenced
+         relation recursively references the same relation.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
   </refsect1>
  
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
new file mode 100644
index 177ac7a..34a699e
*** a/doc/src/sgml/user-manag.sgml
--- b/doc/src/sgml/user-manag.sgml
*************** DROP ROLE <replaceable>name</replaceable
*** 439,442 ****
--- 439,587 ----
    </para>
   </sect1>
  
+  <sect1 id="row-security">
+   <title>Row-security</title>
+   <para>
+    <productname>PostgreSQL</productname> v9.3 or later provides
+    row-security feature, like several commercial database
+    management system. It allows table owner to assign a particular
+    condition that performs as a security policy of the table; only
+    rows that satisfies the condition should be visible, except for
+    a case when superuser runs queries.
+   </para>
+   <para>
+    Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+    command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can contain
+    references to columns of the relation, so it enables to construct
+    arbitrary rule to make access control decision based on contents of
+    each rows.
+   </para>
+   <para>
+    For example, the following <literal>customer</literal> table has
+    <literal>uname</literal> field to store user name, and it assume
+    we don't want to expose any properties of other customers.
+    The following command set <literal>current_user = uname</literal>
+    as row-security policy on the <literal>customer</literal> table.
+ <screen>
+ postgres=> ALTER TABLE customer SET ROW SECURITY
+                                 FOR ALL TO (current_user = uname);
+ ALTER TABLE
+ </screen>
+    <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+    works on the supplied query.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                  QUERY PLAN
+ --------------------------------------------
+  Subquery Scan on customer
+    Filter: f_leak(customer.upasswd)
+    ->  Seq Scan on customer customer_1
+          Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+    This query execution plan means the preconfigured row-security policy is
+    implicitly added, and scan plan on the target relation being wrapped up
+    with a sub-query.
+    It ensures user given qualifiers, including functions with side effects,
+    are never executed earlier than the row-security policy regardless of
+    its cost, except for the cases when these were fully leakproof.
+    This design helps to tackle the scenario described in
+    <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+    qualifiers is significant to keep confidentiality of invisible rows.
+   </para>
+ 
+   <para>
+    On the other hand, this design allows superusers to bypass checks with
+    row-security.
+    It ensures <application>pg_dump</application> can obtain a complete set
+    of database backup, and avoid to execute Trojan horse trap, being injected
+    as a row-security policy of user-defined table, with privileges of
+    superuser.
+   </para>
+ 
+   <para>
+    In case of queries on inherited tables, row-security policy of the parent
+    relation is not applied to child relations. Scope of the row-security
+    policy is limited to the relation on which it is set.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+              QUERY PLAN
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.y)
+          ->  Seq Scan on t1 t1_1
+                Filter: ((x % 2) = 0)
+    ->  Seq Scan on t2
+          Filter: f_leak(y)
+    ->  Subquery Scan on t3
+          Filter: f_leak(t3.y)
+          ->  Seq Scan on t3 t3_1
+                Filter: ((x % 2) = 1)
+ (11 rows)
+ </screen>
+     In the above example, <literal>t1</literal> has inherited
+     child table <literal>t2</literal> and <literal>t3</literal>,
+     and row-security policy is set on only <literal>t1</literal>,
+     and <literal>t3</literal>, not <literal>t2</literal>.
+ 
+     The row-security policy of <literal>t1</literal>, <literal>x</literal>
+     must be even-number, is appended only <literal>t1</literal>,
+     neither <literal>t2</literal> nor <literal>t3</literal>.
+     On the contrary, <literal>t3</literal> has different row-security policy;
+     <literal>x</literal> must be odd-number.
+   </para>
+ 
+   <para>
+    Row-security feature also works to queries for writer-operations; such as
+    <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+    <xref linkend="SQL-DELETE"> commands.
+    It ensures all the modified rows satisfies configured row-security policy.
+    The below query tries to update e-mail address of the
+    <literal>customer</> table, and configured row-security makes sure all the
+    modified rows's <literal>uname</> field has to match with
+    <literal>current_user</>.
+   </para>
+ <screen>
+ postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                     QUERY PLAN
+ --------------------------------------------------
+  Update on customer
+    ->  Subquery Scan on customer
+          ->  Seq Scan on customer customer_1
+                Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+   <para>
+    Modification of a certain table is consist of two different stuffs; one
+    is fetch rows to be modified from the result relation (except for
+    <literal>INSERT</> command), second is insertion of rows to the
+    result relation. Usually, before-row trigger or check constraints are
+    run in the second phase. In addition, row-security policy is also checked
+    on this stage, to prevent to insert or update rows with unprivileged
+    values.
+   </para>
+ 
+   <para>
+    Unlike other commercial database systems, we don't have any plan to allow
+    individual row-security policy for each command type. Even if we want to
+    perform with difference policy between <xref linkend="SQL-SELECT"> and
+    <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+    to be invisible using <xref linkend="SQL-DELETE"> command.
+   </para>
+ 
+   <para>
+    Even though it is not a specific matter in row-security, please be careful
+    if you plan to use <literal>current_user</> in row-level security policy.
+    <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+    switch current user identifier during execution of the query.
+    Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+    then remaining rows with privilege of <literal>bob</>. If and when query
+    execution plan contains some kind of materialization and row-security
+    policy contains <literal>current_user</>, the fetched tuples in
+    <literal>bob</>'s screen might be evaluated according to the privilege of
+    <literal>alice</>.
+   </para>
+  </sect1>
  </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index c4d3f3c..9b4e9f5
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** OBJS = catalog.o dependency.o heap.o ind
*** 15,21 ****
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
--- 15,21 ----
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_rowsecurity.o pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
*************** 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/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index fe17c96..d13b2e1
*** 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"
*************** doDeletion(const ObjectAddress *object, 
*** 1249,1254 ****
--- 1250,1259 ----
  			RemoveEventTriggerById(object->objectId);
  			break;
  
+ 		case OCLASS_ROWSECURITY:
+ 			RemoveRowSecurityById(object->objectId);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized object class: %u",
  				 object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2308,2313 ****
--- 2313,2321 ----
  
  		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 64ca312..7eb7a17
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** InsertPgClassTuple(Relation pg_class_des
*** 791,796 ****
--- 791,797 ----
  	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_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index 4d22f3a..b4bc9d8
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** getObjectDescription(const ObjectAddress
*** 2143,2148 ****
--- 2143,2198 ----
  				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, SnapshotNow, 1, &skey);
+ 				tuple = systable_getnext(sscan);
+ 				if (!HeapTupleIsValid(tuple))
+ 					elog(ERROR, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index ...25739ff
*** a/src/backend/catalog/pg_rowsecurity.c
--- b/src/backend/catalog/pg_rowsecurity.c
***************
*** 0 ****
--- 1,337 ----
+ /* -------------------------------------------------------------------------
+  *
+  * pg_rowsecurity.c
+  *    routines to support manipulation of the pg_rowsecurity catalog
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * -------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "access/genam.h"
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "parser/parse_relation.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/inval.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "utils/tqual.h"
+ 
+ /*
+  * Load row-security policy from the catalog, and keep it on
+  * 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,
+ 							   SnapshotNow, 1, &skey);
+ 	PG_TRY();
+ 	{
+ 		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ 		{
+ 			Datum	value;
+ 			bool	isnull;
+ 			char   *temp;
+ 
+ 			if (!rsdesc)
+ 			{
+ 				rscxt = AllocSetContextCreate(CacheMemoryContext,
+ 											  "Row-security descriptor",
+ 											  ALLOCSET_SMALL_MINSIZE,
+ 											  ALLOCSET_SMALL_INITSIZE,
+ 											  ALLOCSET_SMALL_MAXSIZE);
+ 				oldcxt = MemoryContextSwitchTo(rscxt);
+ 				rsdesc = palloc0(sizeof(RowSecurityDesc));
+ 				rsdesc->rscxt = rscxt;
+ 				MemoryContextSwitchTo(oldcxt);
+ 			}
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 
+ 			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+ 			{
+ 				ereport(WARNING,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("Per-command row-security not implemented")));
+ 				continue;
+ 			}
+ 
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 			temp = TextDatumGetCString(value);
+ 
+ 			oldcxt = MemoryContextSwitchTo(rscxt);
+ 			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+ 			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+ 			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+ 			rsdesc->rsall.hassublinks
+ 				= contain_subplans((Node *)rsdesc->rsall.qual);
+ 			MemoryContextSwitchTo(oldcxt);
+ 
+ 			pfree(temp);
+ 		}
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (rscxt != NULL)
+ 			MemoryContextDelete(rscxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, AccessShareLock);
+ 
+ 	relation->rsdesc = rsdesc;
+ }
+ 
+ /*
+  * Parse the supplied row-security policy, and insert/update a row
+  * of pg_rowsecurity catalog.
+  */
+ static void
+ InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+ {
+ 	Oid				relationId = RelationGetRelid(relation);
+ 	Oid				rowsecId;
+ 	ParseState	   *pstate;
+ 	RangeTblEntry  *rte;
+ 	Node		   *qual;
+ 	Relation		catalog;
+ 	ScanKeyData		skeys[2];
+ 	SysScanDesc		sscan;
+ 	HeapTuple		oldtup;
+ 	HeapTuple		newtup;
+ 	Datum			values[Natts_pg_rowsecurity];
+ 	bool			isnull[Natts_pg_rowsecurity];
+ 	bool			replaces[Natts_pg_rowsecurity];
+ 	ObjectAddress	target;
+ 	ObjectAddress	myself;
+ 
+ 	/* Parse the supplied clause */
+ 	pstate = make_parsestate(NULL);
+ 
+ 	rte = addRangeTableEntryForRelation(pstate, relation,
+ 										NULL, false, false);
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	qual = transformWhereClause(pstate, copyObject(clause),
+ 								EXPR_KIND_ROW_SECURITY,
+ 								"ROW SECURITY");
+ 	/* zero-clear */
+ 	memset(values,   0, sizeof(values));
+ 	memset(replaces, 0, sizeof(replaces));
+ 	memset(isnull,   0, sizeof(isnull));
+ 
+ 	/* Update or Insert an entry to pg_rowsecurity catalog  */
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skeys[0],
+ 				Anum_pg_rowsecurity_rsecrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(RelationGetRelid(relation)));
+ 	ScanKeyInit(&skeys[1],
+ 				Anum_pg_rowsecurity_rseccmd,
+ 				BTEqualStrategyNumber, F_CHAREQ,
+ 				CharGetDatum(rseccmd));
+ 	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+ 							   SnapshotNow, 2, skeys);
+ 	oldtup = systable_getnext(sscan);
+ 	if (HeapTupleIsValid(oldtup))
+ 	{
+ 		rowsecId = HeapTupleGetOid(oldtup);
+ 
+ 		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 
+ 		newtup = heap_modify_tuple(oldtup,
+ 								   RelationGetDescr(catalog),
+ 								   values, isnull, replaces);
+ 		simple_heap_update(catalog, &newtup->t_self, newtup);
+ 
+ 		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+ 	}
+ 	else
+ 	{
+ 		values[Anum_pg_rowsecurity_rsecrelid - 1]
+ 			= ObjectIdGetDatum(relationId);
+ 		values[Anum_pg_rowsecurity_rseccmd - 1]
+ 			= CharGetDatum(rseccmd);
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 		newtup = heap_form_tuple(RelationGetDescr(catalog),
+ 								 values, isnull);
+ 		rowsecId = simple_heap_insert(catalog, newtup);
+ 	}
+ 	CatalogUpdateIndexes(catalog, newtup);
+ 
+ 	heap_freetuple(newtup);
+ 
+ 	/* records dependencies of row-security policy and relation/columns */
+ 	target.classId = RelationRelationId;
+ 	target.objectId = relationId;
+ 	target.objectSubId = 0;
+ 
+ 	myself.classId = RowSecurityRelationId;
+ 	myself.objectId = rowsecId;
+ 	myself.objectSubId = 0;
+ 
+ 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+ 
+ 	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ 						   DEPENDENCY_NORMAL);
+ 	free_parsestate(pstate);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * Remove row-security policy row of pg_rowsecurity
+  */
+ static void
+ DeletePolicyRow(Relation relation, char rseccmd)
+ {
+ 	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+ 
+ 	if (relation->rsdesc)
+ 	{
+ 		ObjectAddress	address;
+ 
+ 		address.classId = RowSecurityRelationId;
+ 		address.objectId = relation->rsdesc->rsall.rsecid;
+ 		address.objectSubId = 0;
+ 
+ 		performDeletion(&address, DROP_RESTRICT, 0);
+ 	}
+ 	else
+ 	{
+ 		/* Nothing to do here */
+ 		elog(INFO, "relation %s has no row-security policy, skipped",
+ 			 RelationGetRelationName(relation));
+ 	}
+ }
+ 
+ /*
+  * Guts of row-security policy deletion.
+  */
+ void
+ RemoveRowSecurityById(Oid rowsecId)
+ {
+ 	Relation	catalog;
+ 	ScanKeyData	skey;
+ 	SysScanDesc	sscan;
+ 	HeapTuple	tuple;
+ 	Relation	rel;
+ 	Oid			relid;
+ 
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skey,
+ 				ObjectIdAttributeNumber,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(rowsecId));
+ 	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+ 							   SnapshotNow, 1, &skey);
+ 	tuple = systable_getnext(sscan);
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+ 
+ 	/*
+ 	 * Open and exclusive-lock the relation the row-security belongs to.
+ 	 */
+ 	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+ 
+ 	rel = heap_open(relid, AccessExclusiveLock);
+ 
+ 	simple_heap_delete(catalog, &tuple->t_self);
+ 
+ 	/* Ensure relcache entries of other session being rebuilt */
+ 	CacheInvalidateRelcache(rel);
+ 
+ 	heap_close(rel, NoLock);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * ALTER TABLE <name> SET ROW SECURITY (...) OR
+  *                    RESET ROW SECURITY
+  */
+ void
+ ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+ {
+ 	Oid			relid = RelationGetRelid(relation);
+ 	char		rseccmd;
+ 
+ 	if (strcmp(cmdname, "all") == 0)
+ 		rseccmd = ROWSECURITY_CMD_ALL;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("Row-security for \"%s\" is not implemented yet",
+ 						cmdname)));
+ 
+ 	if (clause != NULL)
+ 	{
+ 		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+ 
+ 		/*
+ 		 * Also, turn on relhasrowsecurity, if not.
+ 		 */
+ 		if (!RelationGetForm(relation)->relhasrowsecurity)
+ 		{
+ 			Relation	class_rel = heap_open(RelationRelationId,
+ 											  RowExclusiveLock);
+ 			HeapTuple	tuple;
+ 
+ 			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ 			if (!HeapTupleIsValid(tuple))
+ 				elog(ERROR, "cache lookup failed for relation %u", relid);
+ 
+ 			((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);
+ 		}
+ 	}
+ 	else
+ 		DeletePolicyRow(relation, rseccmd);
+ 
+ 	CacheInvalidateRelcache(relation);
+ }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 31819cc..9cb8f1b
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 24,29 ****
--- 24,30 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/heap.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
  #include "commands/copy.h"
***************
*** 34,48 ****
--- 35,53 ----
  #include "libpq/pqformat.h"
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/planner.h"
+ #include "optimizer/rowsecurity.h"
  #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
  #include "rewrite/rewriteHandler.h"
  #include "storage/fd.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/memutils.h"
  #include "utils/portal.h"
  #include "utils/rel.h"
*************** DoCopy(const CopyStmt *stmt, const char 
*** 814,819 ****
--- 819,839 ----
  		rel = heap_openrv(stmt->relation,
  						  (is_from ? RowExclusiveLock : AccessShareLock));
  
+ 		tupDesc = RelationGetDescr(rel);
+ 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+ 
+ 		/*
+ 		 * We have to run regular query, if the target relation has
+  		 * row-level security policy
+ 		 */
+ 		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+ 		{
+ 			heap_close(rel, NoLock);	/* close with keeping lock */
+ 			relid = InvalidOid;
+ 			rel = NULL;
+ 		}
+ 		else
+ 		{
  		relid = RelationGetRelid(rel);
  
  		rte = makeNode(RangeTblEntry);
*************** DoCopy(const CopyStmt *stmt, const char 
*** 822,829 ****
  		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) -
--- 842,847 ----
*************** DoCopy(const CopyStmt *stmt, const char 
*** 835,840 ****
--- 853,859 ----
  				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
  		}
  		ExecCheckRTPerms(list_make1(rte), true);
+ 		}
  	}
  	else
  	{
*************** ProcessCopyOptions(CopyState cstate,
*** 1193,1198 ****
--- 1212,1264 ----
  }
  
  /*
+  * Adjust Query tree constructed with row-level security feature.
+  * If WITH OIDS option was supplied, it adds Var node to reference
+  * object-id system column.
+  */
+ static void
+ fixup_oid_of_rls_query(Query *query)
+ {
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	Var			   *subvar;
+ 	ListCell	   *cell;
+ 	Form_pg_attribute	attform
+ 		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+ 
+ 	subrte = rt_fetch((Index) 1, query->rtable);
+ 	Assert(subrte->rtekind == RTE_RELATION);
+ 
+ 	if (!SearchSysCacheExists2(ATTNUM,
+ 							   ObjectIdGetDatum(subrte->relid),
+ 							   Int16GetDatum(attform->attnum)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 				 errmsg("table \"%s\" does not have OIDs",
+ 						get_rel_name(subrte->relid))));
+ 
+ 	subvar = makeVar((Index) 1,
+ 					 attform->attnum,
+ 					 attform->atttypid,
+ 					 attform->atttypmod,
+ 					 attform->attcollation,
+ 					 0);
+ 	subtle = makeTargetEntry((Expr *) subvar,
+ 							 0,
+ 							 pstrdup(NameStr(attform->attname)),
+ 							 false);
+ 
+ 	query->targetList = list_concat(list_make1(subtle),
+ 									query->targetList);
+ 	/* adjust resno of TargetEntry */
+ 	foreach (cell, query->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 		subtle->resno++;
+ 	}
+ }
+ 
+ /*
   * Common setup routines used by BeginCopyFrom and BeginCopyTo.
   *
   * Iff <binary>, unload or reload in the binary format, as opposed to the
*************** BeginCopy(bool is_from,
*** 1264,1269 ****
--- 1330,1354 ----
  		Assert(!is_from);
  		cstate->rel = NULL;
  
+ 		/*
+ 		 * In case when regular COPY TO was replaced because of row-level
+ 		 * security, "raw_query" node have already analyzed / rewritten
+ 		 * query tree.
+ 		 */
+ 		if (IsA(raw_query, Query))
+ 		{
+ 			query = (Query *) raw_query;
+ 
+ 			Assert(query->querySource == QSRC_ROW_SECURITY);
+ 			if (cstate->oids)
+ 			{
+ 				fixup_oid_of_rls_query(query);
+ 				cstate->oids = false;
+ 			}
+ 			attnamelist = NIL;
+ 		}
+ 		else
+ 		{
  		/* Don't allow COPY w/ OIDs from a select */
  		if (cstate->oids)
  			ereport(ERROR,
*************** BeginCopy(bool is_from,
*** 1288,1293 ****
--- 1373,1379 ----
  			elog(ERROR, "unexpected rewrite result");
  
  		query = (Query *) linitial(rewritten);
+ 		}
  
  		/* The grammar allows SELECT INTO, but we don't support that */
  		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 328e2a8..0374629
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 992,997 ****
--- 992,998 ----
  		case OCLASS_USER_MAPPING:
  		case OCLASS_DEFACL:
  		case OCLASS_EXTENSION:
+ 		case OCLASS_ROWSECURITY:
  			return true;
  
  		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index cb87d90..79f3ff4
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 37,42 ****
--- 37,43 ----
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_opclass.h"
+ #include "catalog/pg_rowsecurity.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
*************** AlterTableGetLockLevel(List *cmds)
*** 2787,2792 ****
--- 2788,2795 ----
  			case AT_SetTableSpace:		/* must rewrite heap */
  			case AT_DropNotNull:		/* may change some SQL plans */
  			case AT_SetNotNull:
+ 			case AT_SetRowSecurity:
+ 			case AT_ResetRowSecurity:
  			case AT_GenericOptions:
  			case AT_AlterColumnGenericOptions:
  				cmd_lockmode = AccessExclusiveLock;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 3155,3160 ****
--- 3158,3165 ----
  		case AT_DropInherit:	/* NO INHERIT */
  		case AT_AddOf:			/* OF */
  		case AT_DropOf: /* NOT OF */
+ 		case AT_SetRowSecurity:
+ 		case AT_ResetRowSecurity:
  			ATSimplePermissions(rel, ATT_TABLE);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 3440,3445 ****
--- 3445,3456 ----
  		case AT_DropOf:
  			ATExecDropOf(rel, lockmode);
  			break;
+ 		case AT_SetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+ 			break;
+ 		case AT_ResetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, NULL);
+ 			break;
  		case AT_GenericOptions:
  			ATExecGenericOptions(rel, (List *) cmd->def);
  			break;
*************** ATExecAlterColumnType(AlteredTableInfo *
*** 7789,7794 ****
--- 7800,7821 ----
  				Assert(defaultexpr);
  				break;
  
+ 			case OCLASS_ROWSECURITY:
+ 				/*
+ 				 * A row-level security policy can depend on a column in case
+ 				 * when the policy clause references a particular column.
+ 				 * Due to same reason why TRIGGER ... WHEN does not support
+ 				 * to change column's type being referenced in clause, row-
+ 				 * level security policy also does not support it.
+ 				 */
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("cannot alter type of a column used in a row-level security policy"),
+ 						 errdetail("%s depends on column \"%s\"",
+ 								   getObjectDescription(&foundObject),
+ 								   colName)));
+ 				break;
+ 
  			case OCLASS_PROC:
  			case OCLASS_TYPE:
  			case OCLASS_CAST:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index bcc6496..5bafcb6
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAppendRelInfo(const AppendRelInfo *
*** 1936,1941 ****
--- 1936,1942 ----
  
  	COPY_SCALAR_FIELD(parent_relid);
  	COPY_SCALAR_FIELD(child_relid);
+ 	COPY_SCALAR_FIELD(child_result);
  	COPY_SCALAR_FIELD(parent_reltype);
  	COPY_SCALAR_FIELD(child_reltype);
  	COPY_NODE_FIELD(translated_vars);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1977,1982 ****
--- 1978,1984 ----
  	COPY_SCALAR_FIELD(relkind);
  	COPY_NODE_FIELD(subquery);
  	COPY_SCALAR_FIELD(security_barrier);
+ 	COPY_SCALAR_FIELD(rowsec_relid);
  	COPY_SCALAR_FIELD(jointype);
  	COPY_NODE_FIELD(joinaliasvars);
  	COPY_NODE_FIELD(funcexpr);
*************** _copyQuery(const Query *from)
*** 2448,2453 ****
--- 2450,2456 ----
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_SCALAR_FIELD(resultRelation);
+ 	COPY_SCALAR_FIELD(sourceRelation);
  	COPY_SCALAR_FIELD(hasAggs);
  	COPY_SCALAR_FIELD(hasWindowFuncs);
  	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 7f9737e..7fd8f3d
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAppendRelInfo(const AppendRelInfo 
*** 807,812 ****
--- 807,813 ----
  {
  	COMPARE_SCALAR_FIELD(parent_relid);
  	COMPARE_SCALAR_FIELD(child_relid);
+ 	COMPARE_SCALAR_FIELD(child_result);
  	COMPARE_SCALAR_FIELD(parent_reltype);
  	COMPARE_SCALAR_FIELD(child_reltype);
  	COMPARE_NODE_FIELD(translated_vars);
*************** _equalQuery(const Query *a, const Query 
*** 842,847 ****
--- 843,849 ----
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
  	COMPARE_SCALAR_FIELD(resultRelation);
+ 	COMPARE_SCALAR_FIELD(sourceRelation);
  	COMPARE_SCALAR_FIELD(hasAggs);
  	COMPARE_SCALAR_FIELD(hasWindowFuncs);
  	COMPARE_SCALAR_FIELD(hasSubLinks);
*************** _equalRangeTblEntry(const RangeTblEntry 
*** 2228,2233 ****
--- 2230,2236 ----
  	COMPARE_SCALAR_FIELD(relkind);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_SCALAR_FIELD(security_barrier);
+ 	COMPARE_SCALAR_FIELD(rowsec_relid);
  	COMPARE_SCALAR_FIELD(jointype);
  	COMPARE_NODE_FIELD(joinaliasvars);
  	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index a896d76..2ed792f
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** query_tree_walker(Query *query,
*** 1877,1884 ****
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (walker((Node *) query->returningList, context))
! 		return true;
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
--- 1877,1887 ----
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 	{
! 		if (walker((Node *) query->returningList, context))
! 			return true;
! 	}
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
*************** query_tree_mutator(Query *query,
*** 2603,2609 ****
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	MUTATE(query->returningList, query->returningList, List *);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
--- 2606,2615 ----
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
!  	if (!(flags & QTW_IGNORE_RETURNING))
!  		MUTATE(query->returningList, query->returningList, List *);
!  	else
!  		query->returningList = copyObject(query->returningList);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 48cd9dc..5b4bbae
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAppendRelInfo(StringInfo str, const 
*** 1923,1928 ****
--- 1923,1929 ----
  
  	WRITE_UINT_FIELD(parent_relid);
  	WRITE_UINT_FIELD(child_relid);
+ 	WRITE_UINT_FIELD(child_result);
  	WRITE_OID_FIELD(parent_reltype);
  	WRITE_OID_FIELD(child_reltype);
  	WRITE_NODE_FIELD(translated_vars);
*************** _outQuery(StringInfo str, const Query *n
*** 2237,2242 ****
--- 2238,2244 ----
  		appendStringInfo(str, " :utilityStmt <>");
  
  	WRITE_INT_FIELD(resultRelation);
+ 	WRITE_INT_FIELD(sourceRelation);
  	WRITE_BOOL_FIELD(hasAggs);
  	WRITE_BOOL_FIELD(hasWindowFuncs);
  	WRITE_BOOL_FIELD(hasSubLinks);
*************** _outRangeTblEntry(StringInfo str, const 
*** 2372,2377 ****
--- 2374,2380 ----
  		case RTE_SUBQUERY:
  			WRITE_NODE_FIELD(subquery);
  			WRITE_BOOL_FIELD(security_barrier);
+ 			WRITE_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index dc9cb3e..6c1af68
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 199,204 ****
--- 199,205 ----
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
  	READ_INT_FIELD(resultRelation);
+ 	READ_INT_FIELD(sourceRelation);
  	READ_BOOL_FIELD(hasAggs);
  	READ_BOOL_FIELD(hasWindowFuncs);
  	READ_BOOL_FIELD(hasSubLinks);
*************** _readRangeTblEntry(void)
*** 1213,1218 ****
--- 1214,1220 ----
  		case RTE_SUBQUERY:
  			READ_NODE_FIELD(subquery);
  			READ_BOOL_FIELD(security_barrier);
+ 			READ_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 01e2fa3..1893516
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
  #include "parser/analyze.h"
*************** standard_planner(Query *parse, int curso
*** 176,181 ****
--- 177,183 ----
  	glob->lastPHId = 0;
  	glob->lastRowMarkId = 0;
  	glob->transientPlan = false;
+ 	glob->planUserId = InvalidOid;
  
  	/* Determine what fraction of the plan is likely to be scanned */
  	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
*************** standard_planner(Query *parse, int curso
*** 253,258 ****
--- 255,261 ----
  	result->relationOids = glob->relationOids;
  	result->invalItems = glob->invalItems;
  	result->nParamExec = glob->nParamExec;
+ 	result->planUserId = glob->planUserId;
  
  	return result;
  }
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 403,408 ****
--- 406,424 ----
  	expand_inherited_tables(root);
  
  	/*
+ 	 * Apply row-security policy of the relation being referenced,
+ 	 * if configured with either of built-in or extension's features.
+ 	 * RangeTblEntry of the relation with row-security policy shall
+ 	 * be replaced with a row-security subquery that has simple scan
+ 	 * on the target relation with row-security policy qualifiers.
+ 	 *
+ 	 * This routine assumes PlannerInfo is already handled with
+ 	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+ 	 * have valid information.
+ 	 */
+ 	apply_row_security_policy(root);
+ 
+ 	/*
  	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
  	 * because preprocess_expression will reduce a constant-true condition to
  	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
*************** inheritance_planner(PlannerInfo *root)
*** 887,892 ****
--- 903,910 ----
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
*************** inheritance_planner(PlannerInfo *root)
*** 950,956 ****
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
--- 968,977 ----
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations,
! 									  (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid));
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index fb67f9e..4abf434
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 37,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
   * preprocess_targetlist
--- 37,84 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				Index result_relation, List *range_table);
  
+ /*
+  * lookup_varattno
+  *
+  * This routine returns an attribute number to reference a particular
+  * attribute. In case when the target relation is really relation,
+  * we can reference arbitrary attribute (including system column)
+  * without any translations. However, we have to translate varattno
+  * of Var that references sub-queries being originated from regular
+  * relations with row-level security policy due to nature of sub-query
+  * that has no system-column.
+  */
+ static AttrNumber
+ lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+ {
+ 	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+ 
+ 	if (rte->rtekind == RTE_SUBQUERY &&
+ 		rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		ListCell   *cell;
+ 
+ 		foreach (cell, rte->subquery->targetList)
+ 		{
+ 			TargetEntry *tle = lfirst(cell);
+ 			Var			*var;
+ 
+ 			if (IsA(tle->expr, Const))
+ 				continue;
+ 
+ 			var = (Var *) tle->expr;
+ 			Assert(IsA(var, Var));
+ 
+ 			if (var->varattno == attno)
+ 				return tle->resno;
+ 		}
+ 		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+ 			 attno);
+ 	}
+ 	return attno;
+ }
  
  /*
   * preprocess_targetlist
*************** preprocess_targetlist(PlannerInfo *root,
*** 73,80 ****
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
--- 113,125 ----
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 	{
+ 		Index	source_relation = (parse->sourceRelation > 0 ?
+ 								   parse->sourceRelation :
+ 								   result_relation);
  		tlist = expand_targetlist(tlist, command_type,
! 								  source_relation, range_table);
! 	}
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
*************** preprocess_targetlist(PlannerInfo *root,
*** 96,102 ****
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  SelfItemPointerAttributeNumber,
  						  TIDOID,
  						  -1,
  						  InvalidOid,
--- 141,148 ----
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  lookup_varattno(SelfItemPointerAttributeNumber,
! 										  rc->rti, range_table),
  						  TIDOID,
  						  -1,
  						  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 112,118 ****
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  TableOidAttributeNumber,
  							  OIDOID,
  							  -1,
  							  InvalidOid,
--- 158,165 ----
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  lookup_varattno(TableOidAttributeNumber,
! 											  rc->rti, range_table),
  							  OIDOID,
  							  -1,
  							  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 130,136 ****
  			/* Not a table, so we need the whole row as a junk var */
  			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
  								  rc->rti,
! 								  0,
  								  false);
  			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
  			tle = makeTargetEntry((Expr *) var,
--- 177,183 ----
  			/* Not a table, so we need the whole row as a junk var */
  			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
  								  rc->rti,
! 								  lookup_varattno(0, rc->rti, range_table),
  								  false);
  			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
  			tle = makeTargetEntry((Expr *) var,
*************** expand_targetlist(List *tlist, int comma
*** 299,305 ****
  					if (!att_tup->attisdropped)
  					{
  						new_expr = (Node *) makeVar(result_relation,
! 													attrno,
  													atttype,
  													atttypmod,
  													attcollation,
--- 346,354 ----
  					if (!att_tup->attisdropped)
  					{
  						new_expr = (Node *) makeVar(result_relation,
! 													lookup_varattno(attrno,
! 														result_relation,
! 														range_table),
  													atttype,
  													atttypmod,
  													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index e249628..0cbb035
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** typedef struct
*** 55,60 ****
--- 55,61 ----
  {
  	PlannerInfo *root;
  	AppendRelInfo *appinfo;
+ 	bool		in_returning;
  } adjust_appendrel_attrs_context;
  
  static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1594,1599 ****
--- 1595,1601 ----
  
  	context.root = root;
  	context.appinfo = appinfo;
+ 	context.in_returning = false;
  
  	/*
  	 * Must be prepared to start with a Query or a bare expression tree.
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1605,1614 ****
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
--- 1607,1635 ----
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES |
! 									 QTW_IGNORE_RETURNING);
! 		/*
! 		 * Returning clause on the relation being replaced with row-
! 		 * security subquery shall be handled in a special way, because
! 		 * of no system columns on subquery.
! 		 * Var references to system column or whole-row reference need
! 		 * to be adjusted to reference artificial columns on behalf of
! 		 * the underlying these columns, however, RETURNGIN clause is
! 		 * an exception because its Var nodes are evaluated towards
! 		 * the "raw" target relation, not a fetched tuple.
! 		 */
! 		context.in_returning = true;
! 		newnode->returningList = (List *)
! 			expression_tree_mutator((Node *) newnode->returningList,
! 									adjust_appendrel_attrs_mutator,
! 									(void *) &context);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid);
! 			newnode->sourceRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1624,1629 ****
--- 1645,1693 ----
  }
  
  static Node *
+ fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+ {
+ 	ListCell   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 	/*
+ 	 * In case when row-level security policy is applied on the referenced
+ 	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+ 	 * to filter out unprivileged rows of underlying relation.
+ 	 * Even though reference to this sub-query should perform as if ones
+ 	 * to real relations, system column has to be cared in special way
+ 	 * due to the nature of sub-query.
+ 	 * Target-entries that reference system columns should be added on
+ 	 * rowlevelsec.c, so all we need to do here is looking up underlying
+ 	 * target-list that can reference underlying system column, and fix-
+ 	 * up varattno of the referencing Var node with resno of TargetEntry.
+ 	 */
+ 	foreach (cell, rte->subquery->targetList)
+ 	{
+ 		TargetEntry *subtle = lfirst(cell);
+ 
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *) subtle->expr;
+ 			Var	   *newnode;
+ 
+ 			if (subvar->varattno == var->varattno)
+ 			{
+ 				newnode = copyObject(var);
+ 				newnode->varattno = subtle->resno;
+ 				return (Node *)newnode;
+ 			}
+ 		}
+ 		else
+ 			Assert(IsA(subtle->expr, Const));
+ 	}
+ 	elog(ERROR, "could not find artificial column of %d, relid %u",
+ 		 var->varattno, var->varno);
+ 	return NULL;
+ }
+ 
+ static Node *
  adjust_appendrel_attrs_mutator(Node *node,
  							   adjust_appendrel_attrs_context *context)
  {
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1638,1645 ****
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = appinfo->child_relid;
  			var->varnoold = appinfo->child_relid;
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
--- 1702,1713 ----
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = (context->in_returning &&
! 						  appinfo->child_result > 0 ?
! 						  appinfo->child_result :
! 						  appinfo->child_relid);
  			var->varnoold = appinfo->child_relid;
+ 
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1664,1669 ****
--- 1732,1745 ----
  				 */
  				if (OidIsValid(appinfo->child_reltype))
  				{
+ 					Query          *parse = context->root->parse;
+ 					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+ 												   parse->rtable);
+ 					if (!context->in_returning &&
+ 						rte->rtekind == RTE_SUBQUERY &&
+ 						rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+ 
  					Assert(var->vartype == appinfo->parent_reltype);
  					if (appinfo->parent_reltype != appinfo->child_reltype)
  					{
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1708,1714 ****
  					return (Node *) rowexpr;
  				}
  			}
! 			/* system attributes don't need any other translation */
  		}
  		return (Node *) var;
  	}
--- 1784,1801 ----
  					return (Node *) rowexpr;
  				}
  			}
! 			else
! 			{
! 				Query		   *parse = context->root->parse;
! 				RangeTblEntry  *rte;
! 
! 				rte = rt_fetch(appinfo->child_relid, parse->rtable);
! 
! 				if (!context->in_returning &&
! 					rte->rtekind == RTE_SUBQUERY &&
! 					rte->subquery->querySource == QSRC_ROW_SECURITY)
! 					return fixup_var_on_rowsec_subquery(rte, var);
! 			}
  		}
  		return (Node *) var;
  	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
new file mode 100644
index 3b2d16b..3f5cb19
*** a/src/backend/optimizer/util/Makefile
--- b/src/backend/optimizer/util/Makefile
*************** top_builddir = ../../../..
*** 13,18 ****
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 13,18 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index ...d686dfa
*** a/src/backend/optimizer/util/rowsecurity.c
--- b/src/backend/optimizer/util/rowsecurity.c
***************
*** 0 ****
--- 1,733 ----
+ /*
+  * optimizer/util/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/plannodes.h"
+ #include "optimizer/clauses.h"
+ #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
+ #include "parser/parsetree.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+ 
+ /* flags to pull row-security policy */
+ #define RSEC_FLAG_HAS_SUBLINKS			0x0001
+ 
+ /* hook to allow extensions to apply their own security policy */
+ row_security_policy_hook_type	row_security_policy_hook = NULL;
+ 
+ /*
+  * make_artificial_column
+  *
+  * It makes a target-entry node that references underlying column.
+  * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+  * if the supplied attribute was already dropped.
+  */
+ static TargetEntry *
+ make_artificial_column(RangeTblEntry *subrte, AttrNumber attnum)
+ {
+ 	Expr   *expr;
+ 	char   *resname;
+ 
+ 	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+ 	if (attnum == InvalidAttrNumber)
+ 	{
+ 		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+ 		resname = get_rel_name(subrte->relid);
+ 	}
+ 	else
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(subrte->relid),
+ 								Int16GetDatum(attnum));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ 				 attnum, subrte->relid);
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		if (attform->attisdropped)
+ 		{
+ 			char	namebuf[NAMEDATALEN];
+ 
+ 			/* Insert NULL just for a placeholder of dropped column */
+ 			expr = (Expr *) makeConst(INT4OID,
+ 									  -1,
+ 									  InvalidOid,
+ 									  sizeof(int32),
+ 									  (Datum) 0,
+ 									  true,		/* isnull */
+ 									  true);	/* byval */
+ 			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+ 			resname = pstrdup(namebuf);
+ 		}
+ 		else
+ 		{
+ 			expr = (Expr *) makeVar((Index) 1,
+ 									attform->attnum,
+ 									attform->atttypid,
+ 									attform->atttypmod,
+ 									attform->attcollation,
+ 									0);
+ 			resname = pstrdup(NameStr(attform->attname));
+ 		}
+ 		ReleaseSysCache(tuple);
+ 	}
+ 	return makeTargetEntry(expr, -1, resname, false);
+ }
+ 
+ /*
+  * lookup_artificial_column
+  *
+  * It looks-up resource number of the target-entry relevant to the given
+  * Var-node that references the row-security subquery. If required column
+  * is not in the subquery's target-list, this function also adds new one
+  * and returns its resource number.
+  */
+ static AttrNumber
+ lookup_artificial_column(PlannerInfo *root,
+ 						 RangeTblEntry *rte, AttrNumber varattno)
+ {
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	ListCell	   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 	subqry = rte->subquery;
+ 	foreach (cell, subqry->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 
+ 		/*
+ 		 * If referenced artifical column is already constructed on the
+ 		 * target-list of row-security subquery, nothing to do any more.
+ 		 */
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *)subtle->expr;
+ 
+ 			Assert(subvar->varno == 1);
+ 			if (subvar->varattno == varattno)
+ 				return subtle->resno;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * OK, we don't have an artifical column relevant to the required ones,
+ 	 * so let's create a new artifical column on demand.
+ 	 */
+ 	subrte = rt_fetch((Index) 1, subqry->rtable);
+ 	subtle = make_artificial_column(subrte, varattno);
+ 	subtle->resno = list_length(subqry->targetList) + 1;
+ 
+ 	subqry->targetList = lappend(subqry->targetList, subtle);
+ 	rte->eref->colnames = lappend(rte->eref->colnames,
+ 								  makeString(pstrdup(subtle->resname)));
+ 	return subtle->resno;
+ }
+ 
+ /*
+  * fixup_varnode_walker
+  *
+  * It recursively fixes up references to the relation to be replaced by
+  * row-security sub-query, and adds artificial columns relevant to the
+  * underlying system columns or whole row-reference on demand.
+  */
+ typedef struct {
+ 	PlannerInfo	*root;
+ 	int		varlevelsup;
+ 	Index  *vartrans;
+ } fixup_varnode_context;
+ 
+ static bool
+ fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	if (IsA(node, Var))
+ 	{
+ 		Var			   *var = (Var *) node;
+ 		List		   *rtable = context->root->parse->rtable;
+ 		RangeTblEntry  *rte;
+ 		ListCell	   *cell;
+ 
+ 		/*
+ 		 * Ignore it, if Var node does not reference the Query currently
+ 		 * we focues on.
+ 		 */
+ 		if (var->varlevelsup != context->varlevelsup)
+ 			return false;
+ 
+ 		/*
+ 		 * Var nodes that reference the relation being replaced by row-
+ 		 * security sub-query has to be adjusted; to reference the sub-
+ 		 * query, instead of the original relation.
+ 		 */
+ 		if (context->vartrans[var->varno] != 0)
+ 		{
+ 			rte = rt_fetch(context->vartrans[var->varno], rtable);
+ 			if (rte->rtekind == RTE_SUBQUERY &&
+ 				rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 			{
+ 				var->varno = var->varnoold = context->vartrans[var->varno];
+ 				var->varattno = lookup_artificial_column(context->root,
+ 														 rte, var->varattno);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			rte = rt_fetch(var->varno, rtable);
+ 			if (!rte->inh)
+ 				return false;
+ 
+ 			foreach (cell, context->root->append_rel_list)
+ 			{
+ 				AppendRelInfo  *appinfo = lfirst(cell);
+ 				RangeTblEntry  *child_rte;
+ 
+ 				if (appinfo->parent_relid != var->varno)
+ 					continue;
+ 
+ 				if (var->varattno > InvalidAttrNumber)
+ 					continue;
+ 
+ 				child_rte = rt_fetch(appinfo->child_relid, rtable);
+ 				if (child_rte->rtekind == RTE_SUBQUERY &&
+ 					child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 					(void) lookup_artificial_column(context->root,
+ 													child_rte,
+ 													var->varattno);
+ 			}
+ 		}
+ 	}
+ 	else if (IsA(node, RangeTblRef))
+ 	{
+ 		RangeTblRef  *rtr = (RangeTblRef *) node;
+ 
+ 		if (context->varlevelsup == 0 &&
+ 			context->vartrans[rtr->rtindex] != 0)
+ 			rtr->rtindex = context->vartrans[rtr->rtindex];
+ 	}
+ 	else if (IsA(node, Query))
+ 	{
+ 		bool	result;
+ 
+ 		context->varlevelsup++;
+ 		result = query_tree_walker((Query *) node,
+ 								   fixup_varnode_walker,
+ 								   (void *) context, 0);
+ 		context->varlevelsup--;
+ 
+ 		return result;
+ 	}
+ 	return expression_tree_walker(node,
+ 								  fixup_varnode_walker,
+ 								  (void *) context);
+ }
+ 
+ /*
+  * check_infinite_recursion
+  *
+  * It is a wrong row-security configuration, if we try to expand
+  * the relation inside of row-security subquery originated from
+  * same relation!
+  */
+ static void
+ check_infinite_recursion(PlannerInfo *root, Oid relid)
+ {
+ 	PlannerInfo	   *parent = root->parent_root;
+ 
+ 	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+ 
+ 		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+ 
+ 		if (relid == rte->relid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("infinite recursion detected for relation \"%s\"",
+ 							get_rel_name(relid))));
+ 		check_infinite_recursion(parent, relid);
+ 	}
+ }
+ 
+ /*
+  * expand_rtentry_with_policy
+  *
+  * It extends a range-table entry of row-security sub-query with supplied
+  * security policy, and append it on the parse->rtable.
+  * This sub-query contains artificial columns that reference underlying
+  * regular columns (at least, references to system column or whole of
+  * table reference shall be added on demand), and simple scan on the
+  * target relation.
+  * Any Var nodes that referenced the relation pointed by rtindex shall
+  * be adjusted to reference this sub-query instead. walker
+  *
+  */
+ static Index
+ expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+ 						   Expr *qual, int flags)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	RangeTblRef	   *subrtr;
+ 	TargetEntry	   *subtle;
+ 	RangeTblEntry  *newrte;
+ 	HeapTuple		tuple;
+ 	AttrNumber		nattrs;
+ 	AttrNumber		attnum;
+ 	List		   *targetList = NIL;
+ 	List		   *colNameList = NIL;
+ 	PlanRowMark	   *rowmark;
+ 
+ 	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+ 
+ 	/* check recursion to prevent infinite loop */
+ 	check_infinite_recursion(root, rte->relid);
+ 
+ 	/* Expand views inside SubLink node */
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+ 
+ 	/*
+ 	 * Construction of sub-query
+ 	 */
+ 	subqry = (Query *) makeNode(Query);
+ 	subqry->commandType = CMD_SELECT;
+ 	subqry->querySource = QSRC_ROW_SECURITY;
+ 
+ 	subrte = copyObject(rte);
+ 	subqry->rtable = list_make1(subrte);
+ 
+ 	subrtr = makeNode(RangeTblRef);
+ 	subrtr->rtindex = 1;
+ 	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		subqry->hasSubLinks = true;
+ 
+ 	/*
+ 	 * Construction of TargetEntries that reference underlying columns.
+ 	 */
+ 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+ 	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+ 	ReleaseSysCache(tuple);
+ 
+ 	for (attnum = 1; attnum <= nattrs; attnum++)
+ 	{
+ 		subtle = make_artificial_column(subrte, attnum);
+ 		subtle->resno = list_length(targetList) + 1;
+ 		Assert(subtle->resno == attnum);
+ 
+ 		targetList = lappend(targetList, subtle);
+ 		colNameList = lappend(colNameList,
+ 							  makeString(pstrdup(subtle->resname)));
+ 	}
+ 	subqry->targetList = targetList;
+ 
+ 	/* Expand RengeTblEntry with this sub-query */
+ 	newrte = makeNode(RangeTblEntry);
+ 	newrte->rtekind = RTE_SUBQUERY;
+ 	newrte->subquery = subqry;
+ 	newrte->security_barrier = true;
+ 	newrte->rowsec_relid = rte->relid;
+ 	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+ 
+ 	parse->rtable = lappend(parse->rtable, newrte);
+ 
+ 	/* Push-down rowmark, if needed */
+ 	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+ 	if (rowmark)
+ 	{
+ 		/*
+ 		 * In case of inherited children, rti/prti of rowmark shall be
+ 		 * fixed up later, on inheritance_planner().
+ 		 */
+ 		if (rowmark->rti == rowmark->prti)
+ 			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+ 		else
+ 			rowmark->rti = list_length(parse->rtable);
+ 
+ 		lookup_artificial_column(root, newrte, SelfItemPointerAttributeNumber);
+ 		lookup_artificial_column(root, newrte, TableOidAttributeNumber);
+ 	}
+ 
+ 	return list_length(parse->rtable);
+ }
+ 
+ /*
+  * pull_row_security_policy
+  *
+  * It pulls the configured row-security policy of both built-in and
+  * extensions. If any, it returns expression tree.
+  */
+ static Expr *
+ pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+ {
+ 	Expr   *quals = NULL;
+ 	int		flags = 0;
+ 
+ 	/*
+ 	 * Pull the row-security policy configured with built-in features,
+ 	 * if unprivileged users. Please note that superuser can bypass it.
+ 	 */
+ 	if (relation->rsdesc && !superuser())
+ 	{
+ 		RowSecurityDesc *rsdesc = relation->rsdesc;
+ 
+ 		quals = copyObject(rsdesc->rsall.qual);
+ 		if (rsdesc->rsall.hassublinks)
+ 			flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 	}
+ 
+ 	/*
+ 	 * Also, ask extensions whether they want to apply their own
+ 	 * row-security policy. If both built-in and extension has
+ 	 * their own policy, it shall be merged.
+ 	 */
+ 	if (row_security_policy_hook)
+ 	{
+ 		List   *temp;
+ 
+ 		temp = (*row_security_policy_hook)(cmd, relation);
+ 		if (temp != NIL)
+ 		{
+ 			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+ 				contain_subplans((Node *) temp))
+ 				flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 
+ 			if (quals != NULL)
+ 				temp = lappend(temp, quals);
+ 
+ 			if (list_length(temp) == 1)
+ 				quals = (Expr *)list_head(temp);
+ 			else if (list_length(temp) > 1)
+ 				quals = makeBoolExpr(AND_EXPR, temp, -1);
+ 		}
+ 	}
+ 	*p_flags = flags;
+ 	return quals;
+ }
+ 
+ /*
+  * copy_row_security_policy
+  *
+  * It construct a row-security subquery instead of raw COPY TO statement,
+  * if target relation has a row-level security policy
+  */
+ bool
+ copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+ {
+ 	Expr		  *quals;
+ 	int			   flags;
+ 	Query		  *parse;
+ 	RangeTblEntry  *rte;
+ 	RangeTblRef	   *rtr;
+ 	TargetEntry	   *tle;
+ 	Var			   *var;
+ 	ListCell	   *cell;
+ 
+ 	if (stmt->is_from)
+ 		return false;
+ 
+ 	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+ 	if (!quals)
+ 		return false;
+ 
+ 	parse = (Query *) makeNode(Query);
+ 	parse->commandType = CMD_SELECT;
+ 	parse->querySource = QSRC_ROW_SECURITY;
+ 
+ 	rte = makeNode(RangeTblEntry);
+ 	rte->rtekind = RTE_RELATION;
+ 	rte->relid = RelationGetRelid(rel);
+ 	rte->relkind = RelationGetForm(rel)->relkind;
+ 
+ 	foreach (cell, attnums)
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 		AttrNumber	attno = lfirst_int(cell);
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(RelationGetRelid(rel)),
+ 								Int16GetDatum(attno));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+ 				 attno, RelationGetRelationName(rel));
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		var = makeVar((Index) 1,
+ 					  attform->attnum,
+ 					  attform->atttypid,
+ 					  attform->atttypmod,
+ 					  attform->attcollation,
+ 					  0);
+ 		tle = makeTargetEntry((Expr *) var,
+ 							  list_length(parse->targetList) + 1,
+ 							  pstrdup(NameStr(attform->attname)),
+ 							  false);
+ 		parse->targetList = lappend(parse->targetList, tle);
+ 
+ 		ReleaseSysCache(tuple);
+ 
+ 		rte->selectedCols = bms_add_member(rte->selectedCols,
+ 								attno - FirstLowInvalidHeapAttributeNumber);
+ 	}
+ 	rte->inFromCl = true;
+ 	rte->requiredPerms = ACL_SELECT;
+ 
+ 	rtr = makeNode(RangeTblRef);
+ 	rtr->rtindex = 1;
+ 
+ 	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+ 	parse->rtable = list_make1(rte);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		parse->hasSubLinks = true;
+ 
+ 	stmt->query = (Node *) parse;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * apply_row_security_relation
+  *
+  * It applies row-security policy on a particular relation being specified.
+  * If this relation is top of the inheritance tree, it also checks inherited
+  * children.
+  */
+ static bool
+ apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+ 							CmdType cmd, Index rtindex)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	bool			result = false;
+ 
+ 	if (!rte->inh)
+ 	{
+ 		Relation	rel;
+ 		Expr	   *qual;
+ 		int			flags = 0;
+ 
+ 		rel = heap_open(rte->relid, NoLock);
+ 		qual = pull_row_security_policy(cmd, rel, &flags);
+ 		if (qual)
+ 		{
+ 			vartrans[rtindex]
+ 				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+ 			if (parse->resultRelation == rtindex)
+ 				parse->sourceRelation = vartrans[rtindex];
+ 			result = true;
+ 		}
+ 		heap_close(rel, NoLock);
+ 	}
+ 	else
+ 	{
+ 		ListCell   *lc1, *lc2;
+ 
+ 		foreach (lc1, root->append_rel_list)
+ 		{
+ 			AppendRelInfo  *apinfo = lfirst(lc1);
+ 
+ 			if (apinfo->parent_relid != rtindex)
+ 				continue;
+ 
+ 			if (apply_row_security_relation(root, vartrans, cmd,
+ 											apinfo->child_relid))
+ 			{
+ 				if (parse->resultRelation == rtindex)
+ 					apinfo->child_result = apinfo->child_relid;
+ 				apinfo->child_relid = vartrans[apinfo->child_relid];
+ 				foreach (lc2, apinfo->translated_vars)
+ 				{
+ 					Var    *var = lfirst(lc2);
+ 
+ 					if (var)
+ 						var->varno = apinfo->child_relid;
+ 				}
+ 				result = true;
+ 			}
+ 		}
+ 	}
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_recursive
+  *
+  * walker on join-tree
+  */
+ static bool
+ apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+ {
+ 	bool	result = false;
+ 
+ 	if (jtnode == NULL)
+ 		return false;
+ 	if (IsA(jtnode, RangeTblRef))
+ 	{
+ 		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+ 		Query		   *parse = root->parse;
+ 		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 		CmdType			cmd;
+ 
+ 		/* Only relation can have row-security policy */
+ 		if (rte->rtekind != RTE_RELATION)
+ 			return false;
+ 
+ 		/*
+ 		 * Prevents infinite recursion. Please note that rtindex == 1
+ 		 * of the row-security subquery is a relation being already
+ 		 * processed on the upper level.
+ 		 */
+ 		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+ 			return false;
+ 
+ 		/* Is it a result relation of UPDATE or DELETE command? */
+ 		if (parse->resultRelation == rtindex)
+ 			cmd = parse->commandType;
+ 		else
+ 			cmd = CMD_SELECT;
+ 
+ 		/* Try to apply row-security policy, if configured */
+ 		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+ 	}
+ 	else if (IsA(jtnode, FromExpr))
+ 	{
+ 		FromExpr   *f = (FromExpr *) jtnode;
+ 		ListCell   *l;
+ 
+ 		foreach (l, f->fromlist)
+ 		{
+ 			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+ 				result = true;
+ 		}
+ 	}
+ 	else if (IsA(jtnode, JoinExpr))
+ 	{
+ 		JoinExpr   *j = (JoinExpr *) jtnode;
+ 
+ 		if (apply_row_security_recursive(root, vartrans, j->larg))
+ 			result = true;
+ 		if (apply_row_security_recursive(root, vartrans, j->rarg))
+ 			result = true;
+ 	}
+ 	else
+ 		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+ 
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_policy
+  *
+  * Entrypoint to apply configured row-security policy of the relation.
+  *
+  * In case when the supplied query references relations with row-security
+  * policy, its RangeTblEntry shall be replaced by a row-security subquery
+  * that has simple scan on the referenced table with policy qualifiers.
+  * Of course, security-barrier shall be set on the subquery to prevent
+  * unexpected push-down of functions without leakproof flag.
+  *
+  * For example, when table t1 has a security policy "(x % 2 = 0)", the
+  * following query:
+  *   SELECT * FROM t1 WHERE f_leak(y)
+  * performs as if
+  *   SELECT * FROM (
+  *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+  *   ) AS t1 WHERE f_leak(y)
+  * would be given. Because the sub-query has security barrier flag, 
+  * configured security policy qualifier is always executed prior to
+  * user given functions.
+  */
+ void
+ apply_row_security_policy(PlannerInfo *root)
+ {
+ 	Query	   *parse = root->parse;
+ 	Oid			curr_userid;
+ 	int			curr_seccxt;
+ 	Index	   *vartrans;
+ 
+ 	/*
+ 	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+ 	 * no row-level security policy should be applied regardless
+ 	 * whether it is built-in or extension.
+ 	 */
+ 	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+ 	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+ 		return;
+ 
+ 	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+ 	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+ 	{
+ 		PlannerGlobal  *glob = root->glob;
+ 		PlanInvalItem  *pi_item;
+ 		fixup_varnode_context context;
+ 
+ 		/*
+ 		 * Constructed Plan with row-level security policy depends on
+ 		 * properties of current user (database superuser can bypass
+ 		 * configured row-security policy!), thus, it has to be
+ 		 * invalidated when its assumption was changed.
+ 		 */
+ 		if (!OidIsValid(glob->planUserId))
+ 		{
+ 			/* Plan invalidation on session user-id */
+ 			glob->planUserId = GetUserId();
+ 
+ 			/* Plan invalidation on catalog updates of pg_authid */
+ 			pi_item = makeNode(PlanInvalItem);
+ 			pi_item->cacheId = AUTHOID;
+ 			pi_item->hashValue =
+ 				GetSysCacheHashValue1(AUTHOID,
+ 									  ObjectIdGetDatum(glob->planUserId));
+ 			glob->invalItems = lappend(glob->invalItems, pi_item);
+ 		}
+ 		else
+ 			Assert(glob->planUserId == GetUserId());
+ 
+ 		/*
+ 		 * Var-nodes that referenced RangeTblEntry to be replaced by
+ 		 * row-security sub-query have to be adjusted for appropriate
+ 		 * reference to the underlying artificial column of the relation.
+ 		 */
+ 		context.root = root;
+ 		context.varlevelsup = 0;
+ 		context.vartrans = vartrans;
+ 		query_tree_walker(parse,
+ 						  fixup_varnode_walker,
+ 						  (void *) &context,
+ 						  QTW_IGNORE_RETURNING);
+ 	}
+ 	pfree(vartrans);
+ }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index d8d2bdf..f685e43
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 256,261 ****
--- 256,262 ----
  
  %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
  %type <list>	alter_table_cmds alter_type_cmds
+ %type <str>		row_security_cmd
  
  %type <dbehavior>	opt_drop_behavior
  
*************** alter_table_cmd:
*** 2174,2179 ****
--- 2175,2198 ----
  					n->def = (Node *)$2;
  					$$ = (Node *)n;
  				}
+ 			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+ 			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_SetRowSecurity;
+ 					n->name = $5;
+ 					n->def = (Node *) $8;
+ 					$$ = (Node *)n;
+ 				}
+ 			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+ 			| RESET ROW SECURITY FOR row_security_cmd
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_ResetRowSecurity;
+ 					n->name = $5;
+ 					n->def = NULL;
+ 					$$ = (Node *)n;
+ 				}
  			| alter_generic_options
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
*************** reloption_elem:
*** 2245,2250 ****
--- 2264,2275 ----
  				}
  		;
  
+ row_security_cmd: ALL	{ $$ = "all"; }
+ 			| SELECT	{ $$ = "select"; }
+ 			| INSERT	{ $$ = "insert"; }
+ 			| UPDATE	{ $$ = "update"; }
+ 			| DELETE_P	{ $$ = "delete"; }
+ 		;
  
  /*****************************************************************************
   *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
new file mode 100644
index 4e4e1cd..d9049e0
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** transformAggregateCall(ParseState *pstat
*** 272,277 ****
--- 272,280 ----
  		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
*** 547,552 ****
--- 550,558 ----
  		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 68b711d..fb09c8c
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformSubLink(ParseState *pstate, Sub
*** 1459,1464 ****
--- 1459,1465 ----
  		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
*** 2640,2645 ****
--- 2641,2648 ----
  			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/parser/parse_relation.c b/src/backend/parser/parse_relation.c
new file mode 100644
index a9254c8..768d531
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
*************** expandRelAttrs(ParseState *pstate, Range
*** 2058,2063 ****
--- 2058,2082 ----
  }
  
  /*
+  * getrelid
+  *
+  * Get OID of the relation corresponding to the given range index.
+  * Note that InvalidOid will be returned if the RTE is for neither
+  * relation nor sub-query originated from a relation
+  */
+ Oid
+ getrelid(Index rangeindex, List *rangetable)
+ {
+ 	RangeTblEntry  *rte = rt_fetch(rangeindex, rangetable);
+ 
+ 	if (rte->rtekind == RTE_RELATION)
+ 		return rte->relid;
+ 	if (rte->rtekind == RTE_SUBQUERY)
+ 		return rte->rowsec_relid;
+ 	return InvalidOid;
+ }
+ 
+ /*
   * get_rte_attribute_name
   *		Get an attribute name from a RangeTblEntry
   *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 7f527bd..2d675ed
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** QueryRewrite(Query *parsetree)
*** 3099,3101 ****
--- 3099,3117 ----
  
  	return results;
  }
+ 
+ /*
+  * QueryRewriteExpr
+  *
+  * This routine provides an entry point of query rewriter towards
+  * a certain expression tree with SubLink node; being added after
+  * the top level query rewrite.
+  * It primarily intends to expand views appeared in the qualifiers
+  * appended with row-level security which needs to modify query
+  * tree at head of the planner stage.
+  */
+ void
+ QueryRewriteExpr(Node *node, List *activeRIRs)
+ {
+ 	fireRIRonSubLink(node, activeRIRs);
+ }
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 65edc1f..b36da97
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3008,3013 ****
--- 3008,3014 ----
  	int			spi_result;
  	Oid			save_userid;
  	int			save_sec_context;
+ 	int			temp_sec_context;
  	Datum		vals[RI_MAX_NUMKEYS * 2];
  	char		nulls[RI_MAX_NUMKEYS * 2];
  
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3087,3094 ****
  
  	/* 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);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
--- 3088,3105 ----
  
  	/* Switch to proper UID to perform check as */
  	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ 
+ 	/*
+ 	 * Row-level security should be disabled in case when 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 (source_is_pk)
+ 		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+ 
  	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! 						   temp_sec_context);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
new file mode 100644
index 26cae97..d05cde7
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "catalog/namespace.h"
  #include "executor/executor.h"
  #include "executor/spi.h"
+ #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/planmain.h"
  #include "optimizer/prep.h"
*************** CheckCachedPlan(CachedPlanSource *planso
*** 794,799 ****
--- 795,810 ----
  		AcquireExecutorLocks(plan->stmt_list, true);
  
  		/*
+ 		 * If plan was constructed with assumption of a particular user-id,
+ 		 * and it is different from the current one, the cached-plan shall
+ 		 * be invalidated to construct suitable query plan.
+ 		 */
+ 		if (plan->is_valid &&
+ 			OidIsValid(plan->planUserId) &&
+ 			plan->planUserId == GetUserId())
+ 			plan->is_valid = false;
+ 
+ 		/*
  		 * If plan was transient, check to see if TransactionXmin has
  		 * advanced, and if so invalidate it.
  		 */
*************** BuildCachedPlan(CachedPlanSource *planso
*** 846,851 ****
--- 857,864 ----
  {
  	CachedPlan *plan;
  	List	   *plist;
+ 	ListCell   *cell;
+ 	Oid			planUserId = InvalidOid;
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
*************** BuildCachedPlan(CachedPlanSource *planso
*** 913,918 ****
--- 926,949 ----
  		PopActiveSnapshot();
  
  	/*
+ 	 * Check whether the generated plan assumes a particular user-id, or not.
+ 	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+ 	 * it should be kept and used to validation check of the cached plan
+ 	 * under the "current" user-id.
+ 	 */
+ 	foreach (cell, plist)
+ 	{
+ 		PlannedStmt	*pstmt = lfirst(cell);
+ 
+ 		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+ 		{
+ 			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+ 
+ 			planUserId = pstmt->planUserId;
+ 		}
+ 	}
+ 
+ 	/*
  	 * Normally we make a dedicated memory context for the CachedPlan and its
  	 * subsidiary data.  (It's probably not going to be large, but just in
  	 * case, use the default maxsize parameter.  It's transient for the
*************** BuildCachedPlan(CachedPlanSource *planso
*** 955,960 ****
--- 986,992 ----
  	plan->is_oneshot = plansource->is_oneshot;
  	plan->is_saved = false;
  	plan->is_valid = true;
+ 	plan->planUserId = planUserId;
  
  	/* assign generation number to new plan */
  	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index 66fb63b..828c10e
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 50,55 ****
--- 50,56 ----
  #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"
*************** RelationBuildDesc(Oid targetRelId, bool 
*** 935,940 ****
--- 936,946 ----
  	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
*** 1842,1847 ****
--- 1848,1855 ----
  		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)
*** 3161,3167 ****
  				relation->rd_rel->relhastriggers = false;
  			restart = true;
  		}
! 
  		/* Release hold on the relation */
  		RelationDecrementReferenceCount(relation);
  
--- 3169,3181 ----
  				relation->rd_rel->relhastriggers = false;
  			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)
*** 4407,4412 ****
--- 4421,4427 ----
  		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 247ad92..9603ef4
*** 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 cd7669b..e9ca251
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3148,3153 ****
--- 3148,3154 ----
  				 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 8beb5d1..84f2519
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static char *myFormatType(const char *ty
*** 249,254 ****
--- 249,255 ----
  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);
*************** dumpBlobs(Archive *fout, void *arg)
*** 2686,2691 ****
--- 2687,2820 ----
  	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_rseccmd;
+ 	int			i_rsecqual;
+ 	int			i, j, ntups;
+ 
+ 	/* row-security is not supported prior to v9.4 */
+ 	if (fout->remoteVersion < 90400)
+ 		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.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_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].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;
+ 
+ 	query = createPQExpBuffer();
+ 	delqry = createPQExpBuffer();
+ 	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	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);
+ 	}
+ 	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+ 	appendPQExpBuffer(delqry, "FOR %s;\n", 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)
*** 4214,4219 ****
--- 4343,4349 ----
  	int			i_relhastriggers;
  	int			i_relhasindex;
  	int			i_relhasrules;
+ 	int			i_relhasrowsec;
  	int			i_relhasoids;
  	int			i_relfrozenxid;
  	int			i_toastoid;
*************** getTables(Archive *fout, int *numTables)
*** 4252,4258 ****
  	 * we cannot correctly identify inherited columns, owned sequences, etc.
  	 */
  
! 	if (fout->remoteVersion >= 90300)
  	{
  		/*
  		 * Left join to pick up dependency info linking sequences to their
--- 4382,4388 ----
  	 * we cannot correctly identify inherited columns, owned sequences, etc.
  	 */
  
! 	if (fout->remoteVersion >= 90400)
  	{
  		/*
  		 * Left join to pick up dependency info linking sequences to their
*************** getTables(Archive *fout, int *numTables)
*** 4264,4269 ****
--- 4394,4438 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "c.relhasrowsecurity, "
+ 						  "c.relfrozenxid, tc.oid AS toid, "
+ 						  "tc.relfrozenxid AS tfrozenxid, "
+ 						  "c.relpersistence, c.relispopulated, "
+ 						  "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(c.reloptions, ', ') AS reloptions, "
+ 						  "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 >= 90300)
+ 	{
+ 		/*
+ 		 * 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, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, c.relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4303,4308 ****
--- 4472,4478 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4340,4345 ****
--- 4510,4516 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4376,4381 ****
--- 4547,4553 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4412,4417 ****
--- 4584,4590 ----
  						  "(%s c.relowner) AS rolname, "
  					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4448,4453 ****
--- 4621,4627 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4484,4489 ****
--- 4658,4664 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4516,4521 ****
--- 4691,4697 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4543,4548 ****
--- 4719,4725 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4580,4585 ****
--- 4757,4763 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4627,4632 ****
--- 4805,4811 ----
  	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_toastoid = PQfnumber(res, "toid");
*************** getTables(Archive *fout, int *numTables)
*** 4675,4680 ****
--- 4854,4860 ----
  		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].relpages = atoi(PQgetvalue(res, i, i_relpages));
*************** dumpDumpableObject(Archive *fout, Dumpab
*** 7758,7763 ****
--- 7938,7946 ----
  						 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 *
*** 14936,14941 ****
--- 15119,15125 ----
  			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 2c5971c..4dc00db
*** 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
*** 244,249 ****
--- 245,251 ----
  	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 */
  	Oid			toast_oid;		/* for restore toast frozen xid */
*************** typedef struct _blobInfo
*** 482,487 ****
--- 484,497 ----
  	char	   *blobacl;
  } BlobInfo;
  
+ typedef struct _rowSecurityInfo
+ {
+ 	DumpableObject dobj;
+ 	TableInfo  *rstable;
+ 	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
*** 573,577 ****
--- 583,588 ----
  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 141e713..5c5777c
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** describeDumpableObject(DumpableObject *o
*** 1342,1347 ****
--- 1342,1352 ----
  					 "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/psql/describe.c b/src/bin/psql/describe.c
new file mode 100644
index 9b6b9c2..db5c0e5
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** listTables(const char *tabtypes, const c
*** 2698,2703 ****
--- 2698,2707 ----
  		appendPQExpBuffer(&buf,
  			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
  						  gettext_noop("Description"));
+ 		if (pset.sversion >= 90300)
+ 			appendPQExpBuffer(&buf,
+ 			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+ 							  gettext_noop("Row-security"));
  	}
  
  	appendPQExpBuffer(&buf,
*************** listTables(const char *tabtypes, const c
*** 2707,2712 ****
--- 2711,2719 ----
  		appendPQExpBuffer(&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 >= 90300)
+ 		appendPQExpBuffer(&buf,
+ 		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
  
  	appendPQExpBuffer(&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 3aefbb5..c96e8d4
*** 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 19268fb..331a64b
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 311,316 ****
--- 311,321 ----
  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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+ #define RowSecurityOidIndexId				3819
+ DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+ #define RowSecurityRelidIndexId				3839
+ 
  /* last step of initialization script: build the indexes declared above */
  BUILD_INDICES
  
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
new file mode 100644
index 49c4f6f..6a16819
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
*************** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI
*** 64,69 ****
--- 64,70 ----
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
  	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+ 	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
  	bool		relhassubclass; /* has (or has had) derived classes */
  	bool		relispopulated; /* matview currently holds query results */
  	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
*************** typedef FormData_pg_class *Form_pg_class
*** 93,99 ****
   * ----------------
   */
  
! #define Natts_pg_class					28
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 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
*************** typedef FormData_pg_class *Form_pg_class
*** 116,127 ****
  #define Anum_pg_class_relhaspkey		20
  #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_relfrozenxid		25
! #define Anum_pg_class_relminmxid		26
! #define Anum_pg_class_relacl			27
! #define Anum_pg_class_reloptions		28
  
  /* ----------------
   *		initial contents of pg_class
--- 117,129 ----
  #define Anum_pg_class_relhaspkey		20
  #define Anum_pg_class_relhasrules		21
  #define Anum_pg_class_relhastriggers	22
! #define Anum_pg_class_relhasrowsecurity	23
! #define Anum_pg_class_relhassubclass	24
! #define Anum_pg_class_relispopulated	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
*************** typedef FormData_pg_class *Form_pg_class
*** 136,148 ****
   * 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 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 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 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 28 0 t f f f f t 3 1 _null_ _null_ ));
  DESCR("");
  
  
--- 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 f t 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 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 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 f t 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 ...a068b35
*** a/src/include/catalog/pg_rowsecurity.h
--- b/src/include/catalog/pg_rowsecurity.h
***************
*** 0 ****
--- 1,76 ----
+ /*
+  * 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_rowlevelsec definition. cpp turns this into
+  *		typedef struct FormData_pg_rowlevelsec
+  * ----------------
+  */
+ #define RowSecurityRelationId	3838
+ 
+ CATALOG(pg_rowsecurity,3838)
+ {
+ 	/* Oid of the relation that has row-security policy */
+ 	Oid				rsecrelid;
+ 
+ 	/* One of ROWSECURITY_CMD_* below */
+ 	char			rseccmd;
+ #ifdef CATALOG_VARLEN
+ 	pg_node_tree	rsecqual;
+ #endif
+ } FormData_pg_rowsecurity;
+ 
+ /* ----------------
+  *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+  *		the format of pg_rowlevelsec relation.
+  * ----------------
+  */
+ typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+ 
+ /* ----------------
+  * 		compiler constants for pg_rowlevelsec
+  * ----------------
+  */
+ #define Natts_pg_rowsecurity				3
+ #define Anum_pg_rowsecurity_rsecrelid		1
+ #define Anum_pg_rowsecurity_rseccmd			2
+ #define Anum_pg_rowsecurity_rsecqual		3
+ 
+ #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
+ {
+ 	MemoryContext		rscxt;
+ 	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+ } RowSecurityDesc;
+ 
+ extern void	RelationBuildRowSecurity(Relation relation);
+ extern void	ATExecSetRowSecurity(Relation relation,
+ 								 const char *cmdname, Node *clause);
+ extern void	RemoveRowSecurityById(Oid relationId);
+ 
+ #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
new file mode 100644
index edced29..bd14531
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
*************** extern int	trace_recovery(int trace_leve
*** 279,284 ****
--- 279,285 ----
  /* 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/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 298af26..93c40f2
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 308,313 ****
--- 308,315 ----
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
   *		projectReturning		for computing a RETURNING list
+  *		rowSecurity				for row-security checks
+  *		rowSecParams			param-list if row-security has SubLink
   * ----------------
   */
  typedef struct ResultRelInfo
*************** typedef struct ResultRelInfo
*** 329,334 ****
--- 331,338 ----
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
  	ProjectionInfo *ri_projectReturning;
+ 	Node	   *ri_rowSecurity;
+ 	List	   *ri_rowSecParams;
  } ResultRelInfo;
  
  /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
new file mode 100644
index d4901ca..cb30110
*** a/src/include/nodes/nodeFuncs.h
--- b/src/include/nodes/nodeFuncs.h
***************
*** 24,29 ****
--- 24,30 ----
  #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
  #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
  #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+ #define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
  
  
  extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 9415e2c..7494b19
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum QuerySource
*** 31,37 ****
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
--- 31,38 ----
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
! 	QSRC_ROW_SECURITY,			/* added by row-security */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
*************** typedef struct Query
*** 112,118 ****
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
--- 113,121 ----
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 	int			sourceRelation;	/* rtable index of source relation for
! 								 * UPDATE/DELETE, if not identical with
! 								 * resultRelation; 0 for elsewhere */
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
*************** typedef struct RangeTblEntry
*** 727,732 ****
--- 730,740 ----
  	 */
  	Query	   *subquery;		/* the sub-query */
  	bool		security_barrier;		/* is from security_barrier view? */
+ 	Oid			rowsec_relid;	/* OID of the original relation, if this
+ 								 * sub-query originated from row-security
+ 								 * policy on the relation. Elsewhere, it
+ 								 * should be InvalidOid.
+ 								 */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
*************** typedef enum AlterTableType
*** 1266,1271 ****
--- 1274,1281 ----
  	AT_DropInherit,				/* NO INHERIT parent */
  	AT_AddOf,					/* OF <type_name> */
  	AT_DropOf,					/* NOT OF */
+ 	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+ 	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
  	AT_GenericOptions			/* OPTIONS (...) */
  } AlterTableType;
  
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index aa4f12c..c040c23
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct PlannedStmt
*** 67,72 ****
--- 67,74 ----
  	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
  
  	int			nParamExec;		/* number of PARAM_EXEC Params used */
+ 
+ 	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index c0a636b..ed01b65
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerGlobal
*** 98,103 ****
--- 98,105 ----
  	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
  
  	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+ 
+ 	Oid			planUserId;		/* User-Id to be assumed on this plan */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
*************** typedef struct AppendRelInfo
*** 1416,1421 ****
--- 1418,1427 ----
  	 */
  	Index		parent_relid;	/* RT index of append parent rel */
  	Index		child_relid;	/* RT index of append child rel */
+ 	Index		child_result;	/* RT index of append child rel's source,
+ 								 * if source of result relation is not
+ 								 * identical. Elsewhere, 0.
+ 								 */
  
  	/*
  	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index ...ff4dd9a
*** a/src/include/optimizer/rowsecurity.h
--- b/src/include/optimizer/rowsecurity.h
***************
*** 0 ****
--- 1,27 ----
+ /* -------------------------------------------------------------------------
+  *
+  * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+ 									 Relation relation, List *attnums);
+ extern void	apply_row_security_policy(PlannerInfo *root);
+ 
+ #endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
new file mode 100644
index bea3b07..3910853
*** 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/parser/parsetree.h b/src/include/parser/parsetree.h
new file mode 100644
index 9608cc6..b4e9df4
*** a/src/include/parser/parsetree.h
--- b/src/include/parser/parsetree.h
***************
*** 32,45 ****
  	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
  
  /*
-  *		getrelid
-  *
   *		Given the range index of a relation, return the corresponding
   *		relation OID.  Note that InvalidOid will be returned if the
   *		RTE is for a non-relation-type RTE.
   */
! #define getrelid(rangeindex,rangetable) \
! 	(rt_fetch(rangeindex, rangetable)->relid)
  
  /*
   * Given an RTE and an attribute number, return the appropriate
--- 32,42 ----
  	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
  
  /*
   *		Given the range index of a relation, return the corresponding
   *		relation OID.  Note that InvalidOid will be returned if the
   *		RTE is for a non-relation-type RTE.
   */
! extern Oid	getrelid(Index rangeindex, List *rangetable);
  
  /*
   * Given an RTE and an attribute number, return the appropriate
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index f0604b0..6b6c364
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/parsenodes.h"
  
  extern List *QueryRewrite(Query *parsetree);
+ extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
  extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
  
  extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
new file mode 100644
index 72f8491..35819fa
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
*************** typedef struct CachedPlan
*** 128,133 ****
--- 128,135 ----
  	bool		is_oneshot;		/* is it a "oneshot" plan? */
  	bool		is_saved;		/* is CachedPlan in a long-lived context? */
  	bool		is_valid;		/* is the stmt_list currently valid? */
+ 	Oid			planUserId;		/* is user-id that is assumed on this cached
+ 								   plan, or InvalidOid if portable for anybody */
  	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
  								 * changes from this value */
  	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 589c9a8..4e4ac65
*** 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
*** 109,114 ****
--- 110,116 ----
  	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 */
  
  	/*
  	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index ...04591df
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
***************
*** 0 ****
--- 1,950 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ 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);
+ 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
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+ (4 rows)
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+   11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+   33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+   44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ERROR:  must be owner of relation document
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ ERROR:  must be owner of relation document
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 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'); -- failed
+ ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+ DETAIL:  Key (cid)=(33) is not present in table "category".
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ 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;
+ 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);
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+ SELECT * FROM t1;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ SELECT * FROM t1 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.b)
+          ->  Seq Scan on t1 t1_1
+                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)
+ 
+ -- reference to system column
+ SELECT oid, * FROM t1;
+  oid | a |  b  
+ -----+---+-----
+  102 | 2 | bbb
+  104 | 4 | ddd
+  201 | 1 | abc
+  203 | 3 | cde
+  301 | 1 | xxx
+  302 | 2 | yyy
+  303 | 3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- reference to whole-row reference
+ SELECT *,t1 FROM t1;
+  a |  b  |   t1    
+ ---+-----+---------
+  2 | bbb | (2,bbb)
+  4 | ddd | (4,ddd)
+  1 | abc | (1,abc)
+  3 | cde | (3,cde)
+  1 | xxx | (1,xxx)
+  2 | yyy | (2,yyy)
+  3 | zzz | (3,zzz)
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT *,t1 FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                ->  Seq Scan on t1 t1_1
+                      Filter: ((a % 2) = 0)
+          ->  Subquery Scan on t2
+                ->  Seq Scan on t2 t2_1
+                      Filter: ((a % 2) = 1)
+          ->  Seq Scan on t3
+ (9 rows)
+ 
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  Seq Scan on t1 t1_1
+                      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)
+ (12 rows)
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ 2	bbb
+ 4	ddd
+ COPY t1 TO stdout WITH OIDS;
+ 102	2	bbb
+ 104	4	ddd
+ COPY t2(c,b) TO stdout WITH OIDS;
+ 201	1.1	abc
+ 203	3.3	cde
+ COPY (SELECT * FROM t1) TO stdout;
+ 2	bbb
+ 4	ddd
+ 1	abc
+ 3	cde
+ 1	xxx
+ 2	yyy
+ 3	zzz
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ ERROR:  table "document" does not have OIDs
+ --
+ -- 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%';
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ ERROR:  infinite recursion detected for relation "s1"
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                      ->  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)
+ (11 rows)
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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
+                      ->  Subquery Scan on s2
+                            Filter: (s2.y ~~ '%af%'::text)
+                            ->  Seq Scan on s2 s2_1
+                                  Filter: ((x % 2) = 0)
+ (11 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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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 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
+  1 | abc
+  1 | xxx
+  2 | yyy
+ (4 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p1(2);
+                      QUERY PLAN                     
+ ----------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a <= 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a <= 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a <= 2)
+ (9 rows)
+ 
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ 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;
+ EXECUTE p2(2);
+  a |  b  
+ ---+-----
+  2 | bbb
+  2 | yyy
+ (2 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p2(2);
+                     QUERY PLAN                     
+ ---------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a = 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a = 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a = 2)
+ (9 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
+    ->  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)
+ 
+ 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
+    ->  Subquery Scan on t1_1
+          Filter: f_leak(t1_1.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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+  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 |             | 
+ (8 rows)
+ 
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+ DROP SCHEMA rls_regress_schema CASCADE;
+ NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index 432d39a..3b9123d
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
*************** SELECT relname, relhasindex
*** 120,125 ****
--- 120,126 ----
   pg_proc                 | t
   pg_range                | t
   pg_rewrite              | t
+  pg_rowsecurity          | t
   pg_seclabel             | t
   pg_shdepend             | t
   pg_shdescription        | t
*************** SELECT relname, relhasindex
*** 166,172 ****
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (155 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 167,173 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (156 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index fd08e8d..367df00
*** 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
  
  # ----------
  # Another group of parallel tests
--- 83,89 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: privileges rowsecurity security_label collate matview lock
  
  # ----------
  # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 1ed059b..5883a64
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: delete
*** 93,98 ****
--- 93,99 ----
  test: namespace
  test: prepared_xacts
  test: privileges
+ test: rowsecurity
  test: security_label
  test: collate
  test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ...55d4aad
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
***************
*** 0 ****
--- 1,298 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ 
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ 
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 
+ -- 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'); -- failed
+ 
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM document;
+ SELECT * FROM category;
+ 
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ 
+ 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);
+ 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
+ \.
+ 
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ COPY t1 TO stdout WITH OIDS;
+ COPY t2(c,b) TO stdout WITH OIDS;
+ COPY (SELECT * FROM t1) TO stdout;
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ 
+ --
+ -- 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%';
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- OK
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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;
+ 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;
+ 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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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;


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] pgsql-v9.4-row-level-security.v3b.patch (169.0K, ../../51E8A8C1.2050700@2ndQuadrant.com/2-pgsql-v9.4-row-level-security.v3b.patch)
  download | inline diff:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index 6715782..6f3c8fc
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 234,239 ****
--- 234,244 ----
       </row>
  
       <row>
+       <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+       <entry>row-level security policy of relation</entry>
+      </row>
+ 
+      <row>
        <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
        <entry>security labels on database objects</entry>
       </row>
***************
*** 1848,1853 ****
--- 1853,1868 ----
       </row>
  
       <row>
+       <entry><structfield>relhasrowsecurity</structfield></entry>
+       <entry><type>bool</type></entry>
+       <entry></entry>
+       <entry>
+        True if table has row-security policy; see
+        <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>relhassubclass</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
***************
*** 5112,5117 ****
--- 5127,5182 ----
  
   </sect1>
  
+  <sect1 id="catalog-pg-rowsecurity">
+   <title><structname>pg_rowlevelsec</structname></title>
+ 
+   <indexterm zone="catalog-pg-rowsecurity">
+    <primary>pg_rowsecurity</primary>
+   </indexterm>
+   <para>
+    The catalog <structname>pg_rowsecurity</structname> stores expression
+    tree of row-security policy to be performed on a particular relation.
+   </para>
+   <table>
+    <title><structname>pg_rowsecurity</structname> Columns</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>References</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>rsecrelid</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+       <entry>The table this row-security is for</entry>
+      </row>
+      <row>
+       <entry><structfield>rseccmd</structfield></entry>
+       <entry><type>char</type></entry>
+       <entry></entry>
+       <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+      </row>
+      <row>
+       <entry><structfield>rsecqual</structfield></entry>
+       <entry><type>pg_node_tree</type></entry>
+       <entry></entry>
+       <entry>An expression tree to be performed as rowl-security policy</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+   <note>
+    <para>
+     <literal>pg_class.relhasrowlevelsec</literal>
+     must be true if a table has row-level security policy in this catalog.
+    </para>
+   </note>
+  </sect1>
  
   <sect1 id="catalog-pg-seclabel">
    <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
new file mode 100644
index 2609d4a..b380852
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 69,80 ****
--- 69,84 ----
      NOT OF
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+     SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+     RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
  
  <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
  
      [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
      { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
      [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ <phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+     { ALL | SELECT | INSERT | UPDATE | DELETE }
  </synopsis>
   </refsynopsisdiv>
  
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 580,585 ****
--- 584,614 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+     <listitem>
+      <para>
+       This form set row-level security policy of the table.
+       Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+       as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+       clause, although mechanism guarantees to evaluate this condition earlier
+       than any other user given condition.
+       <literal>ALL</> is the only supported command type right now.
+       See also <xref linkend="ROW-SECURITY">.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form reset row-level security policy of the table, if exists.
+       <literal>ALL</> is the only supported command type right now.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>RENAME</literal></term>
      <listitem>
       <para>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 821,826 ****
--- 850,869 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">condition</replaceable></term>
+       <listitem>
+        <para>
+         An expression that returns a value of type boolean. Expect for a case
+         when queries are executed with superuser privilege, only rows for which
+         this expression returns true will be fetched, updated or deleted.
+         This expression can reference columns of the relation being configured.
+         Sub-queries can be contained within expression tree, unless referenced
+         relation recursively references the same relation.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
   </refsect1>
  
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
new file mode 100644
index 177ac7a..34a699e
*** a/doc/src/sgml/user-manag.sgml
--- b/doc/src/sgml/user-manag.sgml
*************** DROP ROLE <replaceable>name</replaceable
*** 439,442 ****
--- 439,587 ----
    </para>
   </sect1>
  
+  <sect1 id="row-security">
+   <title>Row-security</title>
+   <para>
+    <productname>PostgreSQL</productname> v9.3 or later provides
+    row-security feature, like several commercial database
+    management system. It allows table owner to assign a particular
+    condition that performs as a security policy of the table; only
+    rows that satisfies the condition should be visible, except for
+    a case when superuser runs queries.
+   </para>
+   <para>
+    Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+    command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can contain
+    references to columns of the relation, so it enables to construct
+    arbitrary rule to make access control decision based on contents of
+    each rows.
+   </para>
+   <para>
+    For example, the following <literal>customer</literal> table has
+    <literal>uname</literal> field to store user name, and it assume
+    we don't want to expose any properties of other customers.
+    The following command set <literal>current_user = uname</literal>
+    as row-security policy on the <literal>customer</literal> table.
+ <screen>
+ postgres=> ALTER TABLE customer SET ROW SECURITY
+                                 FOR ALL TO (current_user = uname);
+ ALTER TABLE
+ </screen>
+    <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+    works on the supplied query.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                  QUERY PLAN
+ --------------------------------------------
+  Subquery Scan on customer
+    Filter: f_leak(customer.upasswd)
+    ->  Seq Scan on customer customer_1
+          Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+    This query execution plan means the preconfigured row-security policy is
+    implicitly added, and scan plan on the target relation being wrapped up
+    with a sub-query.
+    It ensures user given qualifiers, including functions with side effects,
+    are never executed earlier than the row-security policy regardless of
+    its cost, except for the cases when these were fully leakproof.
+    This design helps to tackle the scenario described in
+    <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+    qualifiers is significant to keep confidentiality of invisible rows.
+   </para>
+ 
+   <para>
+    On the other hand, this design allows superusers to bypass checks with
+    row-security.
+    It ensures <application>pg_dump</application> can obtain a complete set
+    of database backup, and avoid to execute Trojan horse trap, being injected
+    as a row-security policy of user-defined table, with privileges of
+    superuser.
+   </para>
+ 
+   <para>
+    In case of queries on inherited tables, row-security policy of the parent
+    relation is not applied to child relations. Scope of the row-security
+    policy is limited to the relation on which it is set.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+              QUERY PLAN
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.y)
+          ->  Seq Scan on t1 t1_1
+                Filter: ((x % 2) = 0)
+    ->  Seq Scan on t2
+          Filter: f_leak(y)
+    ->  Subquery Scan on t3
+          Filter: f_leak(t3.y)
+          ->  Seq Scan on t3 t3_1
+                Filter: ((x % 2) = 1)
+ (11 rows)
+ </screen>
+     In the above example, <literal>t1</literal> has inherited
+     child table <literal>t2</literal> and <literal>t3</literal>,
+     and row-security policy is set on only <literal>t1</literal>,
+     and <literal>t3</literal>, not <literal>t2</literal>.
+ 
+     The row-security policy of <literal>t1</literal>, <literal>x</literal>
+     must be even-number, is appended only <literal>t1</literal>,
+     neither <literal>t2</literal> nor <literal>t3</literal>.
+     On the contrary, <literal>t3</literal> has different row-security policy;
+     <literal>x</literal> must be odd-number.
+   </para>
+ 
+   <para>
+    Row-security feature also works to queries for writer-operations; such as
+    <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+    <xref linkend="SQL-DELETE"> commands.
+    It ensures all the modified rows satisfies configured row-security policy.
+    The below query tries to update e-mail address of the
+    <literal>customer</> table, and configured row-security makes sure all the
+    modified rows's <literal>uname</> field has to match with
+    <literal>current_user</>.
+   </para>
+ <screen>
+ postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                     QUERY PLAN
+ --------------------------------------------------
+  Update on customer
+    ->  Subquery Scan on customer
+          ->  Seq Scan on customer customer_1
+                Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+   <para>
+    Modification of a certain table is consist of two different stuffs; one
+    is fetch rows to be modified from the result relation (except for
+    <literal>INSERT</> command), second is insertion of rows to the
+    result relation. Usually, before-row trigger or check constraints are
+    run in the second phase. In addition, row-security policy is also checked
+    on this stage, to prevent to insert or update rows with unprivileged
+    values.
+   </para>
+ 
+   <para>
+    Unlike other commercial database systems, we don't have any plan to allow
+    individual row-security policy for each command type. Even if we want to
+    perform with difference policy between <xref linkend="SQL-SELECT"> and
+    <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+    to be invisible using <xref linkend="SQL-DELETE"> command.
+   </para>
+ 
+   <para>
+    Even though it is not a specific matter in row-security, please be careful
+    if you plan to use <literal>current_user</> in row-level security policy.
+    <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+    switch current user identifier during execution of the query.
+    Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+    then remaining rows with privilege of <literal>bob</>. If and when query
+    execution plan contains some kind of materialization and row-security
+    policy contains <literal>current_user</>, the fetched tuples in
+    <literal>bob</>'s screen might be evaluated according to the privilege of
+    <literal>alice</>.
+   </para>
+  </sect1>
  </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index c4d3f3c..9b4e9f5
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** OBJS = catalog.o dependency.o heap.o ind
*** 15,21 ****
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
--- 15,21 ----
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_rowsecurity.o pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
*************** 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/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index fe17c96..d13b2e1
*** 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"
*************** doDeletion(const ObjectAddress *object, 
*** 1249,1254 ****
--- 1250,1259 ----
  			RemoveEventTriggerById(object->objectId);
  			break;
  
+ 		case OCLASS_ROWSECURITY:
+ 			RemoveRowSecurityById(object->objectId);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized object class: %u",
  				 object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2308,2313 ****
--- 2313,2321 ----
  
  		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 64ca312..7eb7a17
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** InsertPgClassTuple(Relation pg_class_des
*** 791,796 ****
--- 791,797 ----
  	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_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
new file mode 100644
index 4d22f3a..b4bc9d8
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** getObjectDescription(const ObjectAddress
*** 2143,2148 ****
--- 2143,2198 ----
  				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, SnapshotNow, 1, &skey);
+ 				tuple = systable_getnext(sscan);
+ 				if (!HeapTupleIsValid(tuple))
+ 					elog(ERROR, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index ...25739ff
*** a/src/backend/catalog/pg_rowsecurity.c
--- b/src/backend/catalog/pg_rowsecurity.c
***************
*** 0 ****
--- 1,337 ----
+ /* -------------------------------------------------------------------------
+  *
+  * pg_rowsecurity.c
+  *    routines to support manipulation of the pg_rowsecurity catalog
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * -------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "access/genam.h"
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "parser/parse_relation.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/inval.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "utils/tqual.h"
+ 
+ /*
+  * Load row-security policy from the catalog, and keep it on
+  * 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,
+ 							   SnapshotNow, 1, &skey);
+ 	PG_TRY();
+ 	{
+ 		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ 		{
+ 			Datum	value;
+ 			bool	isnull;
+ 			char   *temp;
+ 
+ 			if (!rsdesc)
+ 			{
+ 				rscxt = AllocSetContextCreate(CacheMemoryContext,
+ 											  "Row-security descriptor",
+ 											  ALLOCSET_SMALL_MINSIZE,
+ 											  ALLOCSET_SMALL_INITSIZE,
+ 											  ALLOCSET_SMALL_MAXSIZE);
+ 				oldcxt = MemoryContextSwitchTo(rscxt);
+ 				rsdesc = palloc0(sizeof(RowSecurityDesc));
+ 				rsdesc->rscxt = rscxt;
+ 				MemoryContextSwitchTo(oldcxt);
+ 			}
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 
+ 			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+ 			{
+ 				ereport(WARNING,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("Per-command row-security not implemented")));
+ 				continue;
+ 			}
+ 
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 			temp = TextDatumGetCString(value);
+ 
+ 			oldcxt = MemoryContextSwitchTo(rscxt);
+ 			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+ 			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+ 			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+ 			rsdesc->rsall.hassublinks
+ 				= contain_subplans((Node *)rsdesc->rsall.qual);
+ 			MemoryContextSwitchTo(oldcxt);
+ 
+ 			pfree(temp);
+ 		}
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (rscxt != NULL)
+ 			MemoryContextDelete(rscxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, AccessShareLock);
+ 
+ 	relation->rsdesc = rsdesc;
+ }
+ 
+ /*
+  * Parse the supplied row-security policy, and insert/update a row
+  * of pg_rowsecurity catalog.
+  */
+ static void
+ InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+ {
+ 	Oid				relationId = RelationGetRelid(relation);
+ 	Oid				rowsecId;
+ 	ParseState	   *pstate;
+ 	RangeTblEntry  *rte;
+ 	Node		   *qual;
+ 	Relation		catalog;
+ 	ScanKeyData		skeys[2];
+ 	SysScanDesc		sscan;
+ 	HeapTuple		oldtup;
+ 	HeapTuple		newtup;
+ 	Datum			values[Natts_pg_rowsecurity];
+ 	bool			isnull[Natts_pg_rowsecurity];
+ 	bool			replaces[Natts_pg_rowsecurity];
+ 	ObjectAddress	target;
+ 	ObjectAddress	myself;
+ 
+ 	/* Parse the supplied clause */
+ 	pstate = make_parsestate(NULL);
+ 
+ 	rte = addRangeTableEntryForRelation(pstate, relation,
+ 										NULL, false, false);
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	qual = transformWhereClause(pstate, copyObject(clause),
+ 								EXPR_KIND_ROW_SECURITY,
+ 								"ROW SECURITY");
+ 	/* zero-clear */
+ 	memset(values,   0, sizeof(values));
+ 	memset(replaces, 0, sizeof(replaces));
+ 	memset(isnull,   0, sizeof(isnull));
+ 
+ 	/* Update or Insert an entry to pg_rowsecurity catalog  */
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skeys[0],
+ 				Anum_pg_rowsecurity_rsecrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(RelationGetRelid(relation)));
+ 	ScanKeyInit(&skeys[1],
+ 				Anum_pg_rowsecurity_rseccmd,
+ 				BTEqualStrategyNumber, F_CHAREQ,
+ 				CharGetDatum(rseccmd));
+ 	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+ 							   SnapshotNow, 2, skeys);
+ 	oldtup = systable_getnext(sscan);
+ 	if (HeapTupleIsValid(oldtup))
+ 	{
+ 		rowsecId = HeapTupleGetOid(oldtup);
+ 
+ 		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 
+ 		newtup = heap_modify_tuple(oldtup,
+ 								   RelationGetDescr(catalog),
+ 								   values, isnull, replaces);
+ 		simple_heap_update(catalog, &newtup->t_self, newtup);
+ 
+ 		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+ 	}
+ 	else
+ 	{
+ 		values[Anum_pg_rowsecurity_rsecrelid - 1]
+ 			= ObjectIdGetDatum(relationId);
+ 		values[Anum_pg_rowsecurity_rseccmd - 1]
+ 			= CharGetDatum(rseccmd);
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 		newtup = heap_form_tuple(RelationGetDescr(catalog),
+ 								 values, isnull);
+ 		rowsecId = simple_heap_insert(catalog, newtup);
+ 	}
+ 	CatalogUpdateIndexes(catalog, newtup);
+ 
+ 	heap_freetuple(newtup);
+ 
+ 	/* records dependencies of row-security policy and relation/columns */
+ 	target.classId = RelationRelationId;
+ 	target.objectId = relationId;
+ 	target.objectSubId = 0;
+ 
+ 	myself.classId = RowSecurityRelationId;
+ 	myself.objectId = rowsecId;
+ 	myself.objectSubId = 0;
+ 
+ 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+ 
+ 	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ 						   DEPENDENCY_NORMAL);
+ 	free_parsestate(pstate);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * Remove row-security policy row of pg_rowsecurity
+  */
+ static void
+ DeletePolicyRow(Relation relation, char rseccmd)
+ {
+ 	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+ 
+ 	if (relation->rsdesc)
+ 	{
+ 		ObjectAddress	address;
+ 
+ 		address.classId = RowSecurityRelationId;
+ 		address.objectId = relation->rsdesc->rsall.rsecid;
+ 		address.objectSubId = 0;
+ 
+ 		performDeletion(&address, DROP_RESTRICT, 0);
+ 	}
+ 	else
+ 	{
+ 		/* Nothing to do here */
+ 		elog(INFO, "relation %s has no row-security policy, skipped",
+ 			 RelationGetRelationName(relation));
+ 	}
+ }
+ 
+ /*
+  * Guts of row-security policy deletion.
+  */
+ void
+ RemoveRowSecurityById(Oid rowsecId)
+ {
+ 	Relation	catalog;
+ 	ScanKeyData	skey;
+ 	SysScanDesc	sscan;
+ 	HeapTuple	tuple;
+ 	Relation	rel;
+ 	Oid			relid;
+ 
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skey,
+ 				ObjectIdAttributeNumber,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(rowsecId));
+ 	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+ 							   SnapshotNow, 1, &skey);
+ 	tuple = systable_getnext(sscan);
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+ 
+ 	/*
+ 	 * Open and exclusive-lock the relation the row-security belongs to.
+ 	 */
+ 	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+ 
+ 	rel = heap_open(relid, AccessExclusiveLock);
+ 
+ 	simple_heap_delete(catalog, &tuple->t_self);
+ 
+ 	/* Ensure relcache entries of other session being rebuilt */
+ 	CacheInvalidateRelcache(rel);
+ 
+ 	heap_close(rel, NoLock);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * ALTER TABLE <name> SET ROW SECURITY (...) OR
+  *                    RESET ROW SECURITY
+  */
+ void
+ ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+ {
+ 	Oid			relid = RelationGetRelid(relation);
+ 	char		rseccmd;
+ 
+ 	if (strcmp(cmdname, "all") == 0)
+ 		rseccmd = ROWSECURITY_CMD_ALL;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("Row-security for \"%s\" is not implemented yet",
+ 						cmdname)));
+ 
+ 	if (clause != NULL)
+ 	{
+ 		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+ 
+ 		/*
+ 		 * Also, turn on relhasrowsecurity, if not.
+ 		 */
+ 		if (!RelationGetForm(relation)->relhasrowsecurity)
+ 		{
+ 			Relation	class_rel = heap_open(RelationRelationId,
+ 											  RowExclusiveLock);
+ 			HeapTuple	tuple;
+ 
+ 			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ 			if (!HeapTupleIsValid(tuple))
+ 				elog(ERROR, "cache lookup failed for relation %u", relid);
+ 
+ 			((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);
+ 		}
+ 	}
+ 	else
+ 		DeletePolicyRow(relation, rseccmd);
+ 
+ 	CacheInvalidateRelcache(relation);
+ }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 31819cc..9cb8f1b
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 24,29 ****
--- 24,30 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/heap.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
  #include "commands/copy.h"
***************
*** 34,48 ****
--- 35,53 ----
  #include "libpq/pqformat.h"
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/planner.h"
+ #include "optimizer/rowsecurity.h"
  #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
  #include "rewrite/rewriteHandler.h"
  #include "storage/fd.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/memutils.h"
  #include "utils/portal.h"
  #include "utils/rel.h"
*************** DoCopy(const CopyStmt *stmt, const char 
*** 814,819 ****
--- 819,839 ----
  		rel = heap_openrv(stmt->relation,
  						  (is_from ? RowExclusiveLock : AccessShareLock));
  
+ 		tupDesc = RelationGetDescr(rel);
+ 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+ 
+ 		/*
+ 		 * We have to run regular query, if the target relation has
+  		 * row-level security policy
+ 		 */
+ 		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+ 		{
+ 			heap_close(rel, NoLock);	/* close with keeping lock */
+ 			relid = InvalidOid;
+ 			rel = NULL;
+ 		}
+ 		else
+ 		{
  		relid = RelationGetRelid(rel);
  
  		rte = makeNode(RangeTblEntry);
*************** DoCopy(const CopyStmt *stmt, const char 
*** 822,829 ****
  		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) -
--- 842,847 ----
*************** DoCopy(const CopyStmt *stmt, const char 
*** 835,840 ****
--- 853,859 ----
  				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
  		}
  		ExecCheckRTPerms(list_make1(rte), true);
+ 		}
  	}
  	else
  	{
*************** ProcessCopyOptions(CopyState cstate,
*** 1193,1198 ****
--- 1212,1264 ----
  }
  
  /*
+  * Adjust Query tree constructed with row-level security feature.
+  * If WITH OIDS option was supplied, it adds Var node to reference
+  * object-id system column.
+  */
+ static void
+ fixup_oid_of_rls_query(Query *query)
+ {
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	Var			   *subvar;
+ 	ListCell	   *cell;
+ 	Form_pg_attribute	attform
+ 		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+ 
+ 	subrte = rt_fetch((Index) 1, query->rtable);
+ 	Assert(subrte->rtekind == RTE_RELATION);
+ 
+ 	if (!SearchSysCacheExists2(ATTNUM,
+ 							   ObjectIdGetDatum(subrte->relid),
+ 							   Int16GetDatum(attform->attnum)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 				 errmsg("table \"%s\" does not have OIDs",
+ 						get_rel_name(subrte->relid))));
+ 
+ 	subvar = makeVar((Index) 1,
+ 					 attform->attnum,
+ 					 attform->atttypid,
+ 					 attform->atttypmod,
+ 					 attform->attcollation,
+ 					 0);
+ 	subtle = makeTargetEntry((Expr *) subvar,
+ 							 0,
+ 							 pstrdup(NameStr(attform->attname)),
+ 							 false);
+ 
+ 	query->targetList = list_concat(list_make1(subtle),
+ 									query->targetList);
+ 	/* adjust resno of TargetEntry */
+ 	foreach (cell, query->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 		subtle->resno++;
+ 	}
+ }
+ 
+ /*
   * Common setup routines used by BeginCopyFrom and BeginCopyTo.
   *
   * Iff <binary>, unload or reload in the binary format, as opposed to the
*************** BeginCopy(bool is_from,
*** 1264,1269 ****
--- 1330,1354 ----
  		Assert(!is_from);
  		cstate->rel = NULL;
  
+ 		/*
+ 		 * In case when regular COPY TO was replaced because of row-level
+ 		 * security, "raw_query" node have already analyzed / rewritten
+ 		 * query tree.
+ 		 */
+ 		if (IsA(raw_query, Query))
+ 		{
+ 			query = (Query *) raw_query;
+ 
+ 			Assert(query->querySource == QSRC_ROW_SECURITY);
+ 			if (cstate->oids)
+ 			{
+ 				fixup_oid_of_rls_query(query);
+ 				cstate->oids = false;
+ 			}
+ 			attnamelist = NIL;
+ 		}
+ 		else
+ 		{
  		/* Don't allow COPY w/ OIDs from a select */
  		if (cstate->oids)
  			ereport(ERROR,
*************** BeginCopy(bool is_from,
*** 1288,1293 ****
--- 1373,1379 ----
  			elog(ERROR, "unexpected rewrite result");
  
  		query = (Query *) linitial(rewritten);
+ 		}
  
  		/* The grammar allows SELECT INTO, but we don't support that */
  		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 328e2a8..0374629
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 992,997 ****
--- 992,998 ----
  		case OCLASS_USER_MAPPING:
  		case OCLASS_DEFACL:
  		case OCLASS_EXTENSION:
+ 		case OCLASS_ROWSECURITY:
  			return true;
  
  		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index cb87d90..79f3ff4
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 37,42 ****
--- 37,43 ----
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_opclass.h"
+ #include "catalog/pg_rowsecurity.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
*************** AlterTableGetLockLevel(List *cmds)
*** 2787,2792 ****
--- 2788,2795 ----
  			case AT_SetTableSpace:		/* must rewrite heap */
  			case AT_DropNotNull:		/* may change some SQL plans */
  			case AT_SetNotNull:
+ 			case AT_SetRowSecurity:
+ 			case AT_ResetRowSecurity:
  			case AT_GenericOptions:
  			case AT_AlterColumnGenericOptions:
  				cmd_lockmode = AccessExclusiveLock;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 3155,3160 ****
--- 3158,3165 ----
  		case AT_DropInherit:	/* NO INHERIT */
  		case AT_AddOf:			/* OF */
  		case AT_DropOf: /* NOT OF */
+ 		case AT_SetRowSecurity:
+ 		case AT_ResetRowSecurity:
  			ATSimplePermissions(rel, ATT_TABLE);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 3440,3445 ****
--- 3445,3456 ----
  		case AT_DropOf:
  			ATExecDropOf(rel, lockmode);
  			break;
+ 		case AT_SetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+ 			break;
+ 		case AT_ResetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, NULL);
+ 			break;
  		case AT_GenericOptions:
  			ATExecGenericOptions(rel, (List *) cmd->def);
  			break;
*************** ATExecAlterColumnType(AlteredTableInfo *
*** 7789,7794 ****
--- 7800,7821 ----
  				Assert(defaultexpr);
  				break;
  
+ 			case OCLASS_ROWSECURITY:
+ 				/*
+ 				 * A row-level security policy can depend on a column in case
+ 				 * when the policy clause references a particular column.
+ 				 * Due to same reason why TRIGGER ... WHEN does not support
+ 				 * to change column's type being referenced in clause, row-
+ 				 * level security policy also does not support it.
+ 				 */
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("cannot alter type of a column used in a row-level security policy"),
+ 						 errdetail("%s depends on column \"%s\"",
+ 								   getObjectDescription(&foundObject),
+ 								   colName)));
+ 				break;
+ 
  			case OCLASS_PROC:
  			case OCLASS_TYPE:
  			case OCLASS_CAST:
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index bcc6496..5bafcb6
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAppendRelInfo(const AppendRelInfo *
*** 1936,1941 ****
--- 1936,1942 ----
  
  	COPY_SCALAR_FIELD(parent_relid);
  	COPY_SCALAR_FIELD(child_relid);
+ 	COPY_SCALAR_FIELD(child_result);
  	COPY_SCALAR_FIELD(parent_reltype);
  	COPY_SCALAR_FIELD(child_reltype);
  	COPY_NODE_FIELD(translated_vars);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1977,1982 ****
--- 1978,1984 ----
  	COPY_SCALAR_FIELD(relkind);
  	COPY_NODE_FIELD(subquery);
  	COPY_SCALAR_FIELD(security_barrier);
+ 	COPY_SCALAR_FIELD(rowsec_relid);
  	COPY_SCALAR_FIELD(jointype);
  	COPY_NODE_FIELD(joinaliasvars);
  	COPY_NODE_FIELD(funcexpr);
*************** _copyQuery(const Query *from)
*** 2448,2453 ****
--- 2450,2456 ----
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_SCALAR_FIELD(resultRelation);
+ 	COPY_SCALAR_FIELD(sourceRelation);
  	COPY_SCALAR_FIELD(hasAggs);
  	COPY_SCALAR_FIELD(hasWindowFuncs);
  	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 7f9737e..7fd8f3d
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAppendRelInfo(const AppendRelInfo 
*** 807,812 ****
--- 807,813 ----
  {
  	COMPARE_SCALAR_FIELD(parent_relid);
  	COMPARE_SCALAR_FIELD(child_relid);
+ 	COMPARE_SCALAR_FIELD(child_result);
  	COMPARE_SCALAR_FIELD(parent_reltype);
  	COMPARE_SCALAR_FIELD(child_reltype);
  	COMPARE_NODE_FIELD(translated_vars);
*************** _equalQuery(const Query *a, const Query 
*** 842,847 ****
--- 843,849 ----
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
  	COMPARE_SCALAR_FIELD(resultRelation);
+ 	COMPARE_SCALAR_FIELD(sourceRelation);
  	COMPARE_SCALAR_FIELD(hasAggs);
  	COMPARE_SCALAR_FIELD(hasWindowFuncs);
  	COMPARE_SCALAR_FIELD(hasSubLinks);
*************** _equalRangeTblEntry(const RangeTblEntry 
*** 2228,2233 ****
--- 2230,2236 ----
  	COMPARE_SCALAR_FIELD(relkind);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_SCALAR_FIELD(security_barrier);
+ 	COMPARE_SCALAR_FIELD(rowsec_relid);
  	COMPARE_SCALAR_FIELD(jointype);
  	COMPARE_NODE_FIELD(joinaliasvars);
  	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index a896d76..2ed792f
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** query_tree_walker(Query *query,
*** 1877,1884 ****
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (walker((Node *) query->returningList, context))
! 		return true;
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
--- 1877,1887 ----
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 	{
! 		if (walker((Node *) query->returningList, context))
! 			return true;
! 	}
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
*************** query_tree_mutator(Query *query,
*** 2603,2609 ****
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	MUTATE(query->returningList, query->returningList, List *);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
--- 2606,2615 ----
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
!  	if (!(flags & QTW_IGNORE_RETURNING))
!  		MUTATE(query->returningList, query->returningList, List *);
!  	else
!  		query->returningList = copyObject(query->returningList);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 48cd9dc..5b4bbae
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAppendRelInfo(StringInfo str, const 
*** 1923,1928 ****
--- 1923,1929 ----
  
  	WRITE_UINT_FIELD(parent_relid);
  	WRITE_UINT_FIELD(child_relid);
+ 	WRITE_UINT_FIELD(child_result);
  	WRITE_OID_FIELD(parent_reltype);
  	WRITE_OID_FIELD(child_reltype);
  	WRITE_NODE_FIELD(translated_vars);
*************** _outQuery(StringInfo str, const Query *n
*** 2237,2242 ****
--- 2238,2244 ----
  		appendStringInfo(str, " :utilityStmt <>");
  
  	WRITE_INT_FIELD(resultRelation);
+ 	WRITE_INT_FIELD(sourceRelation);
  	WRITE_BOOL_FIELD(hasAggs);
  	WRITE_BOOL_FIELD(hasWindowFuncs);
  	WRITE_BOOL_FIELD(hasSubLinks);
*************** _outRangeTblEntry(StringInfo str, const 
*** 2372,2377 ****
--- 2374,2380 ----
  		case RTE_SUBQUERY:
  			WRITE_NODE_FIELD(subquery);
  			WRITE_BOOL_FIELD(security_barrier);
+ 			WRITE_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index dc9cb3e..6c1af68
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 199,204 ****
--- 199,205 ----
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
  	READ_INT_FIELD(resultRelation);
+ 	READ_INT_FIELD(sourceRelation);
  	READ_BOOL_FIELD(hasAggs);
  	READ_BOOL_FIELD(hasWindowFuncs);
  	READ_BOOL_FIELD(hasSubLinks);
*************** _readRangeTblEntry(void)
*** 1213,1218 ****
--- 1214,1220 ----
  		case RTE_SUBQUERY:
  			READ_NODE_FIELD(subquery);
  			READ_BOOL_FIELD(security_barrier);
+ 			READ_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 01e2fa3..1893516
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
  #include "parser/analyze.h"
*************** standard_planner(Query *parse, int curso
*** 176,181 ****
--- 177,183 ----
  	glob->lastPHId = 0;
  	glob->lastRowMarkId = 0;
  	glob->transientPlan = false;
+ 	glob->planUserId = InvalidOid;
  
  	/* Determine what fraction of the plan is likely to be scanned */
  	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
*************** standard_planner(Query *parse, int curso
*** 253,258 ****
--- 255,261 ----
  	result->relationOids = glob->relationOids;
  	result->invalItems = glob->invalItems;
  	result->nParamExec = glob->nParamExec;
+ 	result->planUserId = glob->planUserId;
  
  	return result;
  }
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 403,408 ****
--- 406,424 ----
  	expand_inherited_tables(root);
  
  	/*
+ 	 * Apply row-security policy of the relation being referenced,
+ 	 * if configured with either of built-in or extension's features.
+ 	 * RangeTblEntry of the relation with row-security policy shall
+ 	 * be replaced with a row-security subquery that has simple scan
+ 	 * on the target relation with row-security policy qualifiers.
+ 	 *
+ 	 * This routine assumes PlannerInfo is already handled with
+ 	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+ 	 * have valid information.
+ 	 */
+ 	apply_row_security_policy(root);
+ 
+ 	/*
  	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
  	 * because preprocess_expression will reduce a constant-true condition to
  	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
*************** inheritance_planner(PlannerInfo *root)
*** 887,892 ****
--- 903,910 ----
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
*************** inheritance_planner(PlannerInfo *root)
*** 950,956 ****
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
--- 968,977 ----
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations,
! 									  (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid));
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index fb67f9e..4abf434
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 37,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
   * preprocess_targetlist
--- 37,84 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				Index result_relation, List *range_table);
  
+ /*
+  * lookup_varattno
+  *
+  * This routine returns an attribute number to reference a particular
+  * attribute. In case when the target relation is really relation,
+  * we can reference arbitrary attribute (including system column)
+  * without any translations. However, we have to translate varattno
+  * of Var that references sub-queries being originated from regular
+  * relations with row-level security policy due to nature of sub-query
+  * that has no system-column.
+  */
+ static AttrNumber
+ lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+ {
+ 	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+ 
+ 	if (rte->rtekind == RTE_SUBQUERY &&
+ 		rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		ListCell   *cell;
+ 
+ 		foreach (cell, rte->subquery->targetList)
+ 		{
+ 			TargetEntry *tle = lfirst(cell);
+ 			Var			*var;
+ 
+ 			if (IsA(tle->expr, Const))
+ 				continue;
+ 
+ 			var = (Var *) tle->expr;
+ 			Assert(IsA(var, Var));
+ 
+ 			if (var->varattno == attno)
+ 				return tle->resno;
+ 		}
+ 		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+ 			 attno);
+ 	}
+ 	return attno;
+ }
  
  /*
   * preprocess_targetlist
*************** preprocess_targetlist(PlannerInfo *root,
*** 73,80 ****
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
--- 113,125 ----
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 	{
+ 		Index	source_relation = (parse->sourceRelation > 0 ?
+ 								   parse->sourceRelation :
+ 								   result_relation);
  		tlist = expand_targetlist(tlist, command_type,
! 								  source_relation, range_table);
! 	}
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
*************** preprocess_targetlist(PlannerInfo *root,
*** 96,102 ****
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  SelfItemPointerAttributeNumber,
  						  TIDOID,
  						  -1,
  						  InvalidOid,
--- 141,148 ----
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  lookup_varattno(SelfItemPointerAttributeNumber,
! 										  rc->rti, range_table),
  						  TIDOID,
  						  -1,
  						  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 112,118 ****
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  TableOidAttributeNumber,
  							  OIDOID,
  							  -1,
  							  InvalidOid,
--- 158,165 ----
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  lookup_varattno(TableOidAttributeNumber,
! 											  rc->rti, range_table),
  							  OIDOID,
  							  -1,
  							  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 130,136 ****
  			/* Not a table, so we need the whole row as a junk var */
  			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
  								  rc->rti,
! 								  0,
  								  false);
  			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
  			tle = makeTargetEntry((Expr *) var,
--- 177,183 ----
  			/* Not a table, so we need the whole row as a junk var */
  			var = makeWholeRowVar(rt_fetch(rc->rti, range_table),
  								  rc->rti,
! 								  lookup_varattno(0, rc->rti, range_table),
  								  false);
  			snprintf(resname, sizeof(resname), "wholerow%u", rc->rowmarkId);
  			tle = makeTargetEntry((Expr *) var,
*************** expand_targetlist(List *tlist, int comma
*** 299,305 ****
  					if (!att_tup->attisdropped)
  					{
  						new_expr = (Node *) makeVar(result_relation,
! 													attrno,
  													atttype,
  													atttypmod,
  													attcollation,
--- 346,354 ----
  					if (!att_tup->attisdropped)
  					{
  						new_expr = (Node *) makeVar(result_relation,
! 													lookup_varattno(attrno,
! 														result_relation,
! 														range_table),
  													atttype,
  													atttypmod,
  													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index e249628..0cbb035
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** typedef struct
*** 55,60 ****
--- 55,61 ----
  {
  	PlannerInfo *root;
  	AppendRelInfo *appinfo;
+ 	bool		in_returning;
  } adjust_appendrel_attrs_context;
  
  static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1594,1599 ****
--- 1595,1601 ----
  
  	context.root = root;
  	context.appinfo = appinfo;
+ 	context.in_returning = false;
  
  	/*
  	 * Must be prepared to start with a Query or a bare expression tree.
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1605,1614 ****
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
--- 1607,1635 ----
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES |
! 									 QTW_IGNORE_RETURNING);
! 		/*
! 		 * Returning clause on the relation being replaced with row-
! 		 * security subquery shall be handled in a special way, because
! 		 * of no system columns on subquery.
! 		 * Var references to system column or whole-row reference need
! 		 * to be adjusted to reference artificial columns on behalf of
! 		 * the underlying these columns, however, RETURNGIN clause is
! 		 * an exception because its Var nodes are evaluated towards
! 		 * the "raw" target relation, not a fetched tuple.
! 		 */
! 		context.in_returning = true;
! 		newnode->returningList = (List *)
! 			expression_tree_mutator((Node *) newnode->returningList,
! 									adjust_appendrel_attrs_mutator,
! 									(void *) &context);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid);
! 			newnode->sourceRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1624,1629 ****
--- 1645,1693 ----
  }
  
  static Node *
+ fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+ {
+ 	ListCell   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 	/*
+ 	 * In case when row-level security policy is applied on the referenced
+ 	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+ 	 * to filter out unprivileged rows of underlying relation.
+ 	 * Even though reference to this sub-query should perform as if ones
+ 	 * to real relations, system column has to be cared in special way
+ 	 * due to the nature of sub-query.
+ 	 * Target-entries that reference system columns should be added on
+ 	 * rowlevelsec.c, so all we need to do here is looking up underlying
+ 	 * target-list that can reference underlying system column, and fix-
+ 	 * up varattno of the referencing Var node with resno of TargetEntry.
+ 	 */
+ 	foreach (cell, rte->subquery->targetList)
+ 	{
+ 		TargetEntry *subtle = lfirst(cell);
+ 
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *) subtle->expr;
+ 			Var	   *newnode;
+ 
+ 			if (subvar->varattno == var->varattno)
+ 			{
+ 				newnode = copyObject(var);
+ 				newnode->varattno = subtle->resno;
+ 				return (Node *)newnode;
+ 			}
+ 		}
+ 		else
+ 			Assert(IsA(subtle->expr, Const));
+ 	}
+ 	elog(ERROR, "could not find artificial column of %d, relid %u",
+ 		 var->varattno, var->varno);
+ 	return NULL;
+ }
+ 
+ static Node *
  adjust_appendrel_attrs_mutator(Node *node,
  							   adjust_appendrel_attrs_context *context)
  {
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1638,1645 ****
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = appinfo->child_relid;
  			var->varnoold = appinfo->child_relid;
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
--- 1702,1713 ----
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = (context->in_returning &&
! 						  appinfo->child_result > 0 ?
! 						  appinfo->child_result :
! 						  appinfo->child_relid);
  			var->varnoold = appinfo->child_relid;
+ 
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1664,1669 ****
--- 1732,1745 ----
  				 */
  				if (OidIsValid(appinfo->child_reltype))
  				{
+ 					Query          *parse = context->root->parse;
+ 					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+ 												   parse->rtable);
+ 					if (!context->in_returning &&
+ 						rte->rtekind == RTE_SUBQUERY &&
+ 						rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+ 
  					Assert(var->vartype == appinfo->parent_reltype);
  					if (appinfo->parent_reltype != appinfo->child_reltype)
  					{
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1708,1714 ****
  					return (Node *) rowexpr;
  				}
  			}
! 			/* system attributes don't need any other translation */
  		}
  		return (Node *) var;
  	}
--- 1784,1801 ----
  					return (Node *) rowexpr;
  				}
  			}
! 			else
! 			{
! 				Query		   *parse = context->root->parse;
! 				RangeTblEntry  *rte;
! 
! 				rte = rt_fetch(appinfo->child_relid, parse->rtable);
! 
! 				if (!context->in_returning &&
! 					rte->rtekind == RTE_SUBQUERY &&
! 					rte->subquery->querySource == QSRC_ROW_SECURITY)
! 					return fixup_var_on_rowsec_subquery(rte, var);
! 			}
  		}
  		return (Node *) var;
  	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
new file mode 100644
index 3b2d16b..3f5cb19
*** a/src/backend/optimizer/util/Makefile
--- b/src/backend/optimizer/util/Makefile
*************** top_builddir = ../../../..
*** 13,18 ****
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 13,18 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index ...d686dfa
*** a/src/backend/optimizer/util/rowsecurity.c
--- b/src/backend/optimizer/util/rowsecurity.c
***************
*** 0 ****
--- 1,733 ----
+ /*
+  * optimizer/util/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/plannodes.h"
+ #include "optimizer/clauses.h"
+ #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
+ #include "parser/parsetree.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+ 
+ /* flags to pull row-security policy */
+ #define RSEC_FLAG_HAS_SUBLINKS			0x0001
+ 
+ /* hook to allow extensions to apply their own security policy */
+ row_security_policy_hook_type	row_security_policy_hook = NULL;
+ 
+ /*
+  * make_artificial_column
+  *
+  * It makes a target-entry node that references underlying column.
+  * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+  * if the supplied attribute was already dropped.
+  */
+ static TargetEntry *
+ make_artificial_column(RangeTblEntry *subrte, AttrNumber attnum)
+ {
+ 	Expr   *expr;
+ 	char   *resname;
+ 
+ 	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+ 	if (attnum == InvalidAttrNumber)
+ 	{
+ 		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+ 		resname = get_rel_name(subrte->relid);
+ 	}
+ 	else
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(subrte->relid),
+ 								Int16GetDatum(attnum));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ 				 attnum, subrte->relid);
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		if (attform->attisdropped)
+ 		{
+ 			char	namebuf[NAMEDATALEN];
+ 
+ 			/* Insert NULL just for a placeholder of dropped column */
+ 			expr = (Expr *) makeConst(INT4OID,
+ 									  -1,
+ 									  InvalidOid,
+ 									  sizeof(int32),
+ 									  (Datum) 0,
+ 									  true,		/* isnull */
+ 									  true);	/* byval */
+ 			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+ 			resname = pstrdup(namebuf);
+ 		}
+ 		else
+ 		{
+ 			expr = (Expr *) makeVar((Index) 1,
+ 									attform->attnum,
+ 									attform->atttypid,
+ 									attform->atttypmod,
+ 									attform->attcollation,
+ 									0);
+ 			resname = pstrdup(NameStr(attform->attname));
+ 		}
+ 		ReleaseSysCache(tuple);
+ 	}
+ 	return makeTargetEntry(expr, -1, resname, false);
+ }
+ 
+ /*
+  * lookup_artificial_column
+  *
+  * It looks-up resource number of the target-entry relevant to the given
+  * Var-node that references the row-security subquery. If required column
+  * is not in the subquery's target-list, this function also adds new one
+  * and returns its resource number.
+  */
+ static AttrNumber
+ lookup_artificial_column(PlannerInfo *root,
+ 						 RangeTblEntry *rte, AttrNumber varattno)
+ {
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	ListCell	   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 	subqry = rte->subquery;
+ 	foreach (cell, subqry->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 
+ 		/*
+ 		 * If referenced artifical column is already constructed on the
+ 		 * target-list of row-security subquery, nothing to do any more.
+ 		 */
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *)subtle->expr;
+ 
+ 			Assert(subvar->varno == 1);
+ 			if (subvar->varattno == varattno)
+ 				return subtle->resno;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * OK, we don't have an artifical column relevant to the required ones,
+ 	 * so let's create a new artifical column on demand.
+ 	 */
+ 	subrte = rt_fetch((Index) 1, subqry->rtable);
+ 	subtle = make_artificial_column(subrte, varattno);
+ 	subtle->resno = list_length(subqry->targetList) + 1;
+ 
+ 	subqry->targetList = lappend(subqry->targetList, subtle);
+ 	rte->eref->colnames = lappend(rte->eref->colnames,
+ 								  makeString(pstrdup(subtle->resname)));
+ 	return subtle->resno;
+ }
+ 
+ /*
+  * fixup_varnode_walker
+  *
+  * It recursively fixes up references to the relation to be replaced by
+  * row-security sub-query, and adds artificial columns relevant to the
+  * underlying system columns or whole row-reference on demand.
+  */
+ typedef struct {
+ 	PlannerInfo	*root;
+ 	int		varlevelsup;
+ 	Index  *vartrans;
+ } fixup_varnode_context;
+ 
+ static bool
+ fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	if (IsA(node, Var))
+ 	{
+ 		Var			   *var = (Var *) node;
+ 		List		   *rtable = context->root->parse->rtable;
+ 		RangeTblEntry  *rte;
+ 		ListCell	   *cell;
+ 
+ 		/*
+ 		 * Ignore it, if Var node does not reference the Query currently
+ 		 * we focues on.
+ 		 */
+ 		if (var->varlevelsup != context->varlevelsup)
+ 			return false;
+ 
+ 		/*
+ 		 * Var nodes that reference the relation being replaced by row-
+ 		 * security sub-query has to be adjusted; to reference the sub-
+ 		 * query, instead of the original relation.
+ 		 */
+ 		if (context->vartrans[var->varno] != 0)
+ 		{
+ 			rte = rt_fetch(context->vartrans[var->varno], rtable);
+ 			if (rte->rtekind == RTE_SUBQUERY &&
+ 				rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 			{
+ 				var->varno = var->varnoold = context->vartrans[var->varno];
+ 				var->varattno = lookup_artificial_column(context->root,
+ 														 rte, var->varattno);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			rte = rt_fetch(var->varno, rtable);
+ 			if (!rte->inh)
+ 				return false;
+ 
+ 			foreach (cell, context->root->append_rel_list)
+ 			{
+ 				AppendRelInfo  *appinfo = lfirst(cell);
+ 				RangeTblEntry  *child_rte;
+ 
+ 				if (appinfo->parent_relid != var->varno)
+ 					continue;
+ 
+ 				if (var->varattno > InvalidAttrNumber)
+ 					continue;
+ 
+ 				child_rte = rt_fetch(appinfo->child_relid, rtable);
+ 				if (child_rte->rtekind == RTE_SUBQUERY &&
+ 					child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 					(void) lookup_artificial_column(context->root,
+ 													child_rte,
+ 													var->varattno);
+ 			}
+ 		}
+ 	}
+ 	else if (IsA(node, RangeTblRef))
+ 	{
+ 		RangeTblRef  *rtr = (RangeTblRef *) node;
+ 
+ 		if (context->varlevelsup == 0 &&
+ 			context->vartrans[rtr->rtindex] != 0)
+ 			rtr->rtindex = context->vartrans[rtr->rtindex];
+ 	}
+ 	else if (IsA(node, Query))
+ 	{
+ 		bool	result;
+ 
+ 		context->varlevelsup++;
+ 		result = query_tree_walker((Query *) node,
+ 								   fixup_varnode_walker,
+ 								   (void *) context, 0);
+ 		context->varlevelsup--;
+ 
+ 		return result;
+ 	}
+ 	return expression_tree_walker(node,
+ 								  fixup_varnode_walker,
+ 								  (void *) context);
+ }
+ 
+ /*
+  * check_infinite_recursion
+  *
+  * It is a wrong row-security configuration, if we try to expand
+  * the relation inside of row-security subquery originated from
+  * same relation!
+  */
+ static void
+ check_infinite_recursion(PlannerInfo *root, Oid relid)
+ {
+ 	PlannerInfo	   *parent = root->parent_root;
+ 
+ 	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+ 
+ 		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+ 
+ 		if (relid == rte->relid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("infinite recursion detected for relation \"%s\"",
+ 							get_rel_name(relid))));
+ 		check_infinite_recursion(parent, relid);
+ 	}
+ }
+ 
+ /*
+  * expand_rtentry_with_policy
+  *
+  * It extends a range-table entry of row-security sub-query with supplied
+  * security policy, and append it on the parse->rtable.
+  * This sub-query contains artificial columns that reference underlying
+  * regular columns (at least, references to system column or whole of
+  * table reference shall be added on demand), and simple scan on the
+  * target relation.
+  * Any Var nodes that referenced the relation pointed by rtindex shall
+  * be adjusted to reference this sub-query instead. walker
+  *
+  */
+ static Index
+ expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+ 						   Expr *qual, int flags)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	RangeTblRef	   *subrtr;
+ 	TargetEntry	   *subtle;
+ 	RangeTblEntry  *newrte;
+ 	HeapTuple		tuple;
+ 	AttrNumber		nattrs;
+ 	AttrNumber		attnum;
+ 	List		   *targetList = NIL;
+ 	List		   *colNameList = NIL;
+ 	PlanRowMark	   *rowmark;
+ 
+ 	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+ 
+ 	/* check recursion to prevent infinite loop */
+ 	check_infinite_recursion(root, rte->relid);
+ 
+ 	/* Expand views inside SubLink node */
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+ 
+ 	/*
+ 	 * Construction of sub-query
+ 	 */
+ 	subqry = (Query *) makeNode(Query);
+ 	subqry->commandType = CMD_SELECT;
+ 	subqry->querySource = QSRC_ROW_SECURITY;
+ 
+ 	subrte = copyObject(rte);
+ 	subqry->rtable = list_make1(subrte);
+ 
+ 	subrtr = makeNode(RangeTblRef);
+ 	subrtr->rtindex = 1;
+ 	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		subqry->hasSubLinks = true;
+ 
+ 	/*
+ 	 * Construction of TargetEntries that reference underlying columns.
+ 	 */
+ 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+ 	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+ 	ReleaseSysCache(tuple);
+ 
+ 	for (attnum = 1; attnum <= nattrs; attnum++)
+ 	{
+ 		subtle = make_artificial_column(subrte, attnum);
+ 		subtle->resno = list_length(targetList) + 1;
+ 		Assert(subtle->resno == attnum);
+ 
+ 		targetList = lappend(targetList, subtle);
+ 		colNameList = lappend(colNameList,
+ 							  makeString(pstrdup(subtle->resname)));
+ 	}
+ 	subqry->targetList = targetList;
+ 
+ 	/* Expand RengeTblEntry with this sub-query */
+ 	newrte = makeNode(RangeTblEntry);
+ 	newrte->rtekind = RTE_SUBQUERY;
+ 	newrte->subquery = subqry;
+ 	newrte->security_barrier = true;
+ 	newrte->rowsec_relid = rte->relid;
+ 	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+ 
+ 	parse->rtable = lappend(parse->rtable, newrte);
+ 
+ 	/* Push-down rowmark, if needed */
+ 	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+ 	if (rowmark)
+ 	{
+ 		/*
+ 		 * In case of inherited children, rti/prti of rowmark shall be
+ 		 * fixed up later, on inheritance_planner().
+ 		 */
+ 		if (rowmark->rti == rowmark->prti)
+ 			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+ 		else
+ 			rowmark->rti = list_length(parse->rtable);
+ 
+ 		lookup_artificial_column(root, newrte, SelfItemPointerAttributeNumber);
+ 		lookup_artificial_column(root, newrte, TableOidAttributeNumber);
+ 	}
+ 
+ 	return list_length(parse->rtable);
+ }
+ 
+ /*
+  * pull_row_security_policy
+  *
+  * It pulls the configured row-security policy of both built-in and
+  * extensions. If any, it returns expression tree.
+  */
+ static Expr *
+ pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+ {
+ 	Expr   *quals = NULL;
+ 	int		flags = 0;
+ 
+ 	/*
+ 	 * Pull the row-security policy configured with built-in features,
+ 	 * if unprivileged users. Please note that superuser can bypass it.
+ 	 */
+ 	if (relation->rsdesc && !superuser())
+ 	{
+ 		RowSecurityDesc *rsdesc = relation->rsdesc;
+ 
+ 		quals = copyObject(rsdesc->rsall.qual);
+ 		if (rsdesc->rsall.hassublinks)
+ 			flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 	}
+ 
+ 	/*
+ 	 * Also, ask extensions whether they want to apply their own
+ 	 * row-security policy. If both built-in and extension has
+ 	 * their own policy, it shall be merged.
+ 	 */
+ 	if (row_security_policy_hook)
+ 	{
+ 		List   *temp;
+ 
+ 		temp = (*row_security_policy_hook)(cmd, relation);
+ 		if (temp != NIL)
+ 		{
+ 			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+ 				contain_subplans((Node *) temp))
+ 				flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 
+ 			if (quals != NULL)
+ 				temp = lappend(temp, quals);
+ 
+ 			if (list_length(temp) == 1)
+ 				quals = (Expr *)list_head(temp);
+ 			else if (list_length(temp) > 1)
+ 				quals = makeBoolExpr(AND_EXPR, temp, -1);
+ 		}
+ 	}
+ 	*p_flags = flags;
+ 	return quals;
+ }
+ 
+ /*
+  * copy_row_security_policy
+  *
+  * It construct a row-security subquery instead of raw COPY TO statement,
+  * if target relation has a row-level security policy
+  */
+ bool
+ copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+ {
+ 	Expr		  *quals;
+ 	int			   flags;
+ 	Query		  *parse;
+ 	RangeTblEntry  *rte;
+ 	RangeTblRef	   *rtr;
+ 	TargetEntry	   *tle;
+ 	Var			   *var;
+ 	ListCell	   *cell;
+ 
+ 	if (stmt->is_from)
+ 		return false;
+ 
+ 	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+ 	if (!quals)
+ 		return false;
+ 
+ 	parse = (Query *) makeNode(Query);
+ 	parse->commandType = CMD_SELECT;
+ 	parse->querySource = QSRC_ROW_SECURITY;
+ 
+ 	rte = makeNode(RangeTblEntry);
+ 	rte->rtekind = RTE_RELATION;
+ 	rte->relid = RelationGetRelid(rel);
+ 	rte->relkind = RelationGetForm(rel)->relkind;
+ 
+ 	foreach (cell, attnums)
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 		AttrNumber	attno = lfirst_int(cell);
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(RelationGetRelid(rel)),
+ 								Int16GetDatum(attno));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+ 				 attno, RelationGetRelationName(rel));
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		var = makeVar((Index) 1,
+ 					  attform->attnum,
+ 					  attform->atttypid,
+ 					  attform->atttypmod,
+ 					  attform->attcollation,
+ 					  0);
+ 		tle = makeTargetEntry((Expr *) var,
+ 							  list_length(parse->targetList) + 1,
+ 							  pstrdup(NameStr(attform->attname)),
+ 							  false);
+ 		parse->targetList = lappend(parse->targetList, tle);
+ 
+ 		ReleaseSysCache(tuple);
+ 
+ 		rte->selectedCols = bms_add_member(rte->selectedCols,
+ 								attno - FirstLowInvalidHeapAttributeNumber);
+ 	}
+ 	rte->inFromCl = true;
+ 	rte->requiredPerms = ACL_SELECT;
+ 
+ 	rtr = makeNode(RangeTblRef);
+ 	rtr->rtindex = 1;
+ 
+ 	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+ 	parse->rtable = list_make1(rte);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		parse->hasSubLinks = true;
+ 
+ 	stmt->query = (Node *) parse;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * apply_row_security_relation
+  *
+  * It applies row-security policy on a particular relation being specified.
+  * If this relation is top of the inheritance tree, it also checks inherited
+  * children.
+  */
+ static bool
+ apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+ 							CmdType cmd, Index rtindex)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	bool			result = false;
+ 
+ 	if (!rte->inh)
+ 	{
+ 		Relation	rel;
+ 		Expr	   *qual;
+ 		int			flags = 0;
+ 
+ 		rel = heap_open(rte->relid, NoLock);
+ 		qual = pull_row_security_policy(cmd, rel, &flags);
+ 		if (qual)
+ 		{
+ 			vartrans[rtindex]
+ 				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+ 			if (parse->resultRelation == rtindex)
+ 				parse->sourceRelation = vartrans[rtindex];
+ 			result = true;
+ 		}
+ 		heap_close(rel, NoLock);
+ 	}
+ 	else
+ 	{
+ 		ListCell   *lc1, *lc2;
+ 
+ 		foreach (lc1, root->append_rel_list)
+ 		{
+ 			AppendRelInfo  *apinfo = lfirst(lc1);
+ 
+ 			if (apinfo->parent_relid != rtindex)
+ 				continue;
+ 
+ 			if (apply_row_security_relation(root, vartrans, cmd,
+ 											apinfo->child_relid))
+ 			{
+ 				if (parse->resultRelation == rtindex)
+ 					apinfo->child_result = apinfo->child_relid;
+ 				apinfo->child_relid = vartrans[apinfo->child_relid];
+ 				foreach (lc2, apinfo->translated_vars)
+ 				{
+ 					Var    *var = lfirst(lc2);
+ 
+ 					if (var)
+ 						var->varno = apinfo->child_relid;
+ 				}
+ 				result = true;
+ 			}
+ 		}
+ 	}
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_recursive
+  *
+  * walker on join-tree
+  */
+ static bool
+ apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+ {
+ 	bool	result = false;
+ 
+ 	if (jtnode == NULL)
+ 		return false;
+ 	if (IsA(jtnode, RangeTblRef))
+ 	{
+ 		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+ 		Query		   *parse = root->parse;
+ 		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 		CmdType			cmd;
+ 
+ 		/* Only relation can have row-security policy */
+ 		if (rte->rtekind != RTE_RELATION)
+ 			return false;
+ 
+ 		/*
+ 		 * Prevents infinite recursion. Please note that rtindex == 1
+ 		 * of the row-security subquery is a relation being already
+ 		 * processed on the upper level.
+ 		 */
+ 		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+ 			return false;
+ 
+ 		/* Is it a result relation of UPDATE or DELETE command? */
+ 		if (parse->resultRelation == rtindex)
+ 			cmd = parse->commandType;
+ 		else
+ 			cmd = CMD_SELECT;
+ 
+ 		/* Try to apply row-security policy, if configured */
+ 		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+ 	}
+ 	else if (IsA(jtnode, FromExpr))
+ 	{
+ 		FromExpr   *f = (FromExpr *) jtnode;
+ 		ListCell   *l;
+ 
+ 		foreach (l, f->fromlist)
+ 		{
+ 			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+ 				result = true;
+ 		}
+ 	}
+ 	else if (IsA(jtnode, JoinExpr))
+ 	{
+ 		JoinExpr   *j = (JoinExpr *) jtnode;
+ 
+ 		if (apply_row_security_recursive(root, vartrans, j->larg))
+ 			result = true;
+ 		if (apply_row_security_recursive(root, vartrans, j->rarg))
+ 			result = true;
+ 	}
+ 	else
+ 		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+ 
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_policy
+  *
+  * Entrypoint to apply configured row-security policy of the relation.
+  *
+  * In case when the supplied query references relations with row-security
+  * policy, its RangeTblEntry shall be replaced by a row-security subquery
+  * that has simple scan on the referenced table with policy qualifiers.
+  * Of course, security-barrier shall be set on the subquery to prevent
+  * unexpected push-down of functions without leakproof flag.
+  *
+  * For example, when table t1 has a security policy "(x % 2 = 0)", the
+  * following query:
+  *   SELECT * FROM t1 WHERE f_leak(y)
+  * performs as if
+  *   SELECT * FROM (
+  *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+  *   ) AS t1 WHERE f_leak(y)
+  * would be given. Because the sub-query has security barrier flag, 
+  * configured security policy qualifier is always executed prior to
+  * user given functions.
+  */
+ void
+ apply_row_security_policy(PlannerInfo *root)
+ {
+ 	Query	   *parse = root->parse;
+ 	Oid			curr_userid;
+ 	int			curr_seccxt;
+ 	Index	   *vartrans;
+ 
+ 	/*
+ 	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+ 	 * no row-level security policy should be applied regardless
+ 	 * whether it is built-in or extension.
+ 	 */
+ 	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+ 	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+ 		return;
+ 
+ 	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+ 	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+ 	{
+ 		PlannerGlobal  *glob = root->glob;
+ 		PlanInvalItem  *pi_item;
+ 		fixup_varnode_context context;
+ 
+ 		/*
+ 		 * Constructed Plan with row-level security policy depends on
+ 		 * properties of current user (database superuser can bypass
+ 		 * configured row-security policy!), thus, it has to be
+ 		 * invalidated when its assumption was changed.
+ 		 */
+ 		if (!OidIsValid(glob->planUserId))
+ 		{
+ 			/* Plan invalidation on session user-id */
+ 			glob->planUserId = GetUserId();
+ 
+ 			/* Plan invalidation on catalog updates of pg_authid */
+ 			pi_item = makeNode(PlanInvalItem);
+ 			pi_item->cacheId = AUTHOID;
+ 			pi_item->hashValue =
+ 				GetSysCacheHashValue1(AUTHOID,
+ 									  ObjectIdGetDatum(glob->planUserId));
+ 			glob->invalItems = lappend(glob->invalItems, pi_item);
+ 		}
+ 		else
+ 			Assert(glob->planUserId == GetUserId());
+ 
+ 		/*
+ 		 * Var-nodes that referenced RangeTblEntry to be replaced by
+ 		 * row-security sub-query have to be adjusted for appropriate
+ 		 * reference to the underlying artificial column of the relation.
+ 		 */
+ 		context.root = root;
+ 		context.varlevelsup = 0;
+ 		context.vartrans = vartrans;
+ 		query_tree_walker(parse,
+ 						  fixup_varnode_walker,
+ 						  (void *) &context,
+ 						  QTW_IGNORE_RETURNING);
+ 	}
+ 	pfree(vartrans);
+ }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index d8d2bdf..f685e43
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 256,261 ****
--- 256,262 ----
  
  %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
  %type <list>	alter_table_cmds alter_type_cmds
+ %type <str>		row_security_cmd
  
  %type <dbehavior>	opt_drop_behavior
  
*************** alter_table_cmd:
*** 2174,2179 ****
--- 2175,2198 ----
  					n->def = (Node *)$2;
  					$$ = (Node *)n;
  				}
+ 			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+ 			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_SetRowSecurity;
+ 					n->name = $5;
+ 					n->def = (Node *) $8;
+ 					$$ = (Node *)n;
+ 				}
+ 			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+ 			| RESET ROW SECURITY FOR row_security_cmd
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_ResetRowSecurity;
+ 					n->name = $5;
+ 					n->def = NULL;
+ 					$$ = (Node *)n;
+ 				}
  			| alter_generic_options
  				{
  					AlterTableCmd *n = makeNode(AlterTableCmd);
*************** reloption_elem:
*** 2245,2250 ****
--- 2264,2275 ----
  				}
  		;
  
+ row_security_cmd: ALL	{ $$ = "all"; }
+ 			| SELECT	{ $$ = "select"; }
+ 			| INSERT	{ $$ = "insert"; }
+ 			| UPDATE	{ $$ = "update"; }
+ 			| DELETE_P	{ $$ = "delete"; }
+ 		;
  
  /*****************************************************************************
   *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
new file mode 100644
index 4e4e1cd..d9049e0
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** transformAggregateCall(ParseState *pstat
*** 272,277 ****
--- 272,280 ----
  		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
*** 547,552 ****
--- 550,558 ----
  		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 68b711d..fb09c8c
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformSubLink(ParseState *pstate, Sub
*** 1459,1464 ****
--- 1459,1465 ----
  		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
*** 2640,2645 ****
--- 2641,2648 ----
  			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/parser/parse_relation.c b/src/backend/parser/parse_relation.c
new file mode 100644
index a9254c8..768d531
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
*************** expandRelAttrs(ParseState *pstate, Range
*** 2058,2063 ****
--- 2058,2082 ----
  }
  
  /*
+  * getrelid
+  *
+  * Get OID of the relation corresponding to the given range index.
+  * Note that InvalidOid will be returned if the RTE is for neither
+  * relation nor sub-query originated from a relation
+  */
+ Oid
+ getrelid(Index rangeindex, List *rangetable)
+ {
+ 	RangeTblEntry  *rte = rt_fetch(rangeindex, rangetable);
+ 
+ 	if (rte->rtekind == RTE_RELATION)
+ 		return rte->relid;
+ 	if (rte->rtekind == RTE_SUBQUERY)
+ 		return rte->rowsec_relid;
+ 	return InvalidOid;
+ }
+ 
+ /*
   * get_rte_attribute_name
   *		Get an attribute name from a RangeTblEntry
   *
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 7f527bd..2d675ed
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** QueryRewrite(Query *parsetree)
*** 3099,3101 ****
--- 3099,3117 ----
  
  	return results;
  }
+ 
+ /*
+  * QueryRewriteExpr
+  *
+  * This routine provides an entry point of query rewriter towards
+  * a certain expression tree with SubLink node; being added after
+  * the top level query rewrite.
+  * It primarily intends to expand views appeared in the qualifiers
+  * appended with row-level security which needs to modify query
+  * tree at head of the planner stage.
+  */
+ void
+ QueryRewriteExpr(Node *node, List *activeRIRs)
+ {
+ 	fireRIRonSubLink(node, activeRIRs);
+ }
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 65edc1f..b36da97
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3008,3013 ****
--- 3008,3014 ----
  	int			spi_result;
  	Oid			save_userid;
  	int			save_sec_context;
+ 	int			temp_sec_context;
  	Datum		vals[RI_MAX_NUMKEYS * 2];
  	char		nulls[RI_MAX_NUMKEYS * 2];
  
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3087,3094 ****
  
  	/* 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);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
--- 3088,3105 ----
  
  	/* Switch to proper UID to perform check as */
  	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ 
+ 	/*
+ 	 * Row-level security should be disabled in case when 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 (source_is_pk)
+ 		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+ 
  	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! 						   temp_sec_context);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
new file mode 100644
index 26cae97..d05cde7
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "catalog/namespace.h"
  #include "executor/executor.h"
  #include "executor/spi.h"
+ #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/planmain.h"
  #include "optimizer/prep.h"
*************** CheckCachedPlan(CachedPlanSource *planso
*** 794,799 ****
--- 795,810 ----
  		AcquireExecutorLocks(plan->stmt_list, true);
  
  		/*
+ 		 * If plan was constructed with assumption of a particular user-id,
+ 		 * and it is different from the current one, the cached-plan shall
+ 		 * be invalidated to construct suitable query plan.
+ 		 */
+ 		if (plan->is_valid &&
+ 			OidIsValid(plan->planUserId) &&
+ 			plan->planUserId == GetUserId())
+ 			plan->is_valid = false;
+ 
+ 		/*
  		 * If plan was transient, check to see if TransactionXmin has
  		 * advanced, and if so invalidate it.
  		 */
*************** BuildCachedPlan(CachedPlanSource *planso
*** 846,851 ****
--- 857,864 ----
  {
  	CachedPlan *plan;
  	List	   *plist;
+ 	ListCell   *cell;
+ 	Oid			planUserId = InvalidOid;
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
*************** BuildCachedPlan(CachedPlanSource *planso
*** 913,918 ****
--- 926,949 ----
  		PopActiveSnapshot();
  
  	/*
+ 	 * Check whether the generated plan assumes a particular user-id, or not.
+ 	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+ 	 * it should be kept and used to validation check of the cached plan
+ 	 * under the "current" user-id.
+ 	 */
+ 	foreach (cell, plist)
+ 	{
+ 		PlannedStmt	*pstmt = lfirst(cell);
+ 
+ 		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+ 		{
+ 			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+ 
+ 			planUserId = pstmt->planUserId;
+ 		}
+ 	}
+ 
+ 	/*
  	 * Normally we make a dedicated memory context for the CachedPlan and its
  	 * subsidiary data.  (It's probably not going to be large, but just in
  	 * case, use the default maxsize parameter.  It's transient for the
*************** BuildCachedPlan(CachedPlanSource *planso
*** 955,960 ****
--- 986,992 ----
  	plan->is_oneshot = plansource->is_oneshot;
  	plan->is_saved = false;
  	plan->is_valid = true;
+ 	plan->planUserId = planUserId;
  
  	/* assign generation number to new plan */
  	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index 66fb63b..828c10e
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 50,55 ****
--- 50,56 ----
  #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"
*************** RelationBuildDesc(Oid targetRelId, bool 
*** 935,940 ****
--- 936,946 ----
  	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
*** 1842,1847 ****
--- 1848,1855 ----
  		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)
*** 3161,3167 ****
  				relation->rd_rel->relhastriggers = false;
  			restart = true;
  		}
! 
  		/* Release hold on the relation */
  		RelationDecrementReferenceCount(relation);
  
--- 3169,3181 ----
  				relation->rd_rel->relhastriggers = false;
  			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)
*** 4407,4412 ****
--- 4421,4427 ----
  		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 247ad92..9603ef4
*** 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 cd7669b..e9ca251
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3148,3153 ****
--- 3148,3154 ----
  				 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 8beb5d1..84f2519
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static char *myFormatType(const char *ty
*** 249,254 ****
--- 249,255 ----
  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);
*************** dumpBlobs(Archive *fout, void *arg)
*** 2686,2691 ****
--- 2687,2820 ----
  	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_rseccmd;
+ 	int			i_rsecqual;
+ 	int			i, j, ntups;
+ 
+ 	/* row-security is not supported prior to v9.4 */
+ 	if (fout->remoteVersion < 90400)
+ 		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.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_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].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;
+ 
+ 	query = createPQExpBuffer();
+ 	delqry = createPQExpBuffer();
+ 	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	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);
+ 	}
+ 	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+ 	appendPQExpBuffer(delqry, "FOR %s;\n", 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)
*** 4214,4219 ****
--- 4343,4349 ----
  	int			i_relhastriggers;
  	int			i_relhasindex;
  	int			i_relhasrules;
+ 	int			i_relhasrowsec;
  	int			i_relhasoids;
  	int			i_relfrozenxid;
  	int			i_toastoid;
*************** getTables(Archive *fout, int *numTables)
*** 4252,4258 ****
  	 * we cannot correctly identify inherited columns, owned sequences, etc.
  	 */
  
! 	if (fout->remoteVersion >= 90300)
  	{
  		/*
  		 * Left join to pick up dependency info linking sequences to their
--- 4382,4388 ----
  	 * we cannot correctly identify inherited columns, owned sequences, etc.
  	 */
  
! 	if (fout->remoteVersion >= 90400)
  	{
  		/*
  		 * Left join to pick up dependency info linking sequences to their
*************** getTables(Archive *fout, int *numTables)
*** 4264,4269 ****
--- 4394,4438 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "c.relhasrowsecurity, "
+ 						  "c.relfrozenxid, tc.oid AS toid, "
+ 						  "tc.relfrozenxid AS tfrozenxid, "
+ 						  "c.relpersistence, c.relispopulated, "
+ 						  "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(c.reloptions, ', ') AS reloptions, "
+ 						  "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 >= 90300)
+ 	{
+ 		/*
+ 		 * 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, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, c.relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4303,4308 ****
--- 4472,4478 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4340,4345 ****
--- 4510,4516 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4376,4381 ****
--- 4547,4553 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4412,4417 ****
--- 4584,4590 ----
  						  "(%s c.relowner) AS rolname, "
  					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4448,4453 ****
--- 4621,4627 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4484,4489 ****
--- 4658,4664 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4516,4521 ****
--- 4691,4697 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4543,4548 ****
--- 4719,4725 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4580,4585 ****
--- 4757,4763 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4627,4632 ****
--- 4805,4811 ----
  	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_toastoid = PQfnumber(res, "toid");
*************** getTables(Archive *fout, int *numTables)
*** 4675,4680 ****
--- 4854,4860 ----
  		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].relpages = atoi(PQgetvalue(res, i, i_relpages));
*************** dumpDumpableObject(Archive *fout, Dumpab
*** 7758,7763 ****
--- 7938,7946 ----
  						 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 *
*** 14936,14941 ****
--- 15119,15125 ----
  			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 2c5971c..4dc00db
*** 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
*** 244,249 ****
--- 245,251 ----
  	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 */
  	Oid			toast_oid;		/* for restore toast frozen xid */
*************** typedef struct _blobInfo
*** 482,487 ****
--- 484,497 ----
  	char	   *blobacl;
  } BlobInfo;
  
+ typedef struct _rowSecurityInfo
+ {
+ 	DumpableObject dobj;
+ 	TableInfo  *rstable;
+ 	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
*** 573,577 ****
--- 583,588 ----
  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 141e713..5c5777c
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** describeDumpableObject(DumpableObject *o
*** 1342,1347 ****
--- 1342,1352 ----
  					 "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/psql/describe.c b/src/bin/psql/describe.c
new file mode 100644
index 9b6b9c2..db5c0e5
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** listTables(const char *tabtypes, const c
*** 2698,2703 ****
--- 2698,2707 ----
  		appendPQExpBuffer(&buf,
  			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
  						  gettext_noop("Description"));
+ 		if (pset.sversion >= 90300)
+ 			appendPQExpBuffer(&buf,
+ 			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+ 							  gettext_noop("Row-security"));
  	}
  
  	appendPQExpBuffer(&buf,
*************** listTables(const char *tabtypes, const c
*** 2707,2712 ****
--- 2711,2719 ----
  		appendPQExpBuffer(&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 >= 90300)
+ 		appendPQExpBuffer(&buf,
+ 		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
  
  	appendPQExpBuffer(&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 3aefbb5..c96e8d4
*** 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 19268fb..331a64b
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 311,316 ****
--- 311,321 ----
  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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+ #define RowSecurityOidIndexId				3819
+ DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+ #define RowSecurityRelidIndexId				3839
+ 
  /* last step of initialization script: build the indexes declared above */
  BUILD_INDICES
  
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
new file mode 100644
index 49c4f6f..6a16819
*** a/src/include/catalog/pg_class.h
--- b/src/include/catalog/pg_class.h
*************** CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI
*** 64,69 ****
--- 64,70 ----
  	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
  	bool		relhasrules;	/* has (or has had) any rules */
  	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+ 	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
  	bool		relhassubclass; /* has (or has had) derived classes */
  	bool		relispopulated; /* matview currently holds query results */
  	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
*************** typedef FormData_pg_class *Form_pg_class
*** 93,99 ****
   * ----------------
   */
  
! #define Natts_pg_class					28
  #define Anum_pg_class_relname			1
  #define Anum_pg_class_relnamespace		2
  #define Anum_pg_class_reltype			3
--- 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
*************** typedef FormData_pg_class *Form_pg_class
*** 116,127 ****
  #define Anum_pg_class_relhaspkey		20
  #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_relfrozenxid		25
! #define Anum_pg_class_relminmxid		26
! #define Anum_pg_class_relacl			27
! #define Anum_pg_class_reloptions		28
  
  /* ----------------
   *		initial contents of pg_class
--- 117,129 ----
  #define Anum_pg_class_relhaspkey		20
  #define Anum_pg_class_relhasrules		21
  #define Anum_pg_class_relhastriggers	22
! #define Anum_pg_class_relhasrowsecurity	23
! #define Anum_pg_class_relhassubclass	24
! #define Anum_pg_class_relispopulated	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
*************** typedef FormData_pg_class *Form_pg_class
*** 136,148 ****
   * 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 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 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 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 28 0 t f f f f t 3 1 _null_ _null_ ));
  DESCR("");
  
  
--- 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 f t 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 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 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 f t 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 ...a068b35
*** a/src/include/catalog/pg_rowsecurity.h
--- b/src/include/catalog/pg_rowsecurity.h
***************
*** 0 ****
--- 1,76 ----
+ /*
+  * 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_rowlevelsec definition. cpp turns this into
+  *		typedef struct FormData_pg_rowlevelsec
+  * ----------------
+  */
+ #define RowSecurityRelationId	3838
+ 
+ CATALOG(pg_rowsecurity,3838)
+ {
+ 	/* Oid of the relation that has row-security policy */
+ 	Oid				rsecrelid;
+ 
+ 	/* One of ROWSECURITY_CMD_* below */
+ 	char			rseccmd;
+ #ifdef CATALOG_VARLEN
+ 	pg_node_tree	rsecqual;
+ #endif
+ } FormData_pg_rowsecurity;
+ 
+ /* ----------------
+  *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+  *		the format of pg_rowlevelsec relation.
+  * ----------------
+  */
+ typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+ 
+ /* ----------------
+  * 		compiler constants for pg_rowlevelsec
+  * ----------------
+  */
+ #define Natts_pg_rowsecurity				3
+ #define Anum_pg_rowsecurity_rsecrelid		1
+ #define Anum_pg_rowsecurity_rseccmd			2
+ #define Anum_pg_rowsecurity_rsecqual		3
+ 
+ #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
+ {
+ 	MemoryContext		rscxt;
+ 	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+ } RowSecurityDesc;
+ 
+ extern void	RelationBuildRowSecurity(Relation relation);
+ extern void	ATExecSetRowSecurity(Relation relation,
+ 								 const char *cmdname, Node *clause);
+ extern void	RemoveRowSecurityById(Oid relationId);
+ 
+ #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
new file mode 100644
index edced29..bd14531
*** a/src/include/miscadmin.h
--- b/src/include/miscadmin.h
*************** extern int	trace_recovery(int trace_leve
*** 279,284 ****
--- 279,285 ----
  /* 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/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 298af26..93c40f2
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 308,313 ****
--- 308,315 ----
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
   *		projectReturning		for computing a RETURNING list
+  *		rowSecurity				for row-security checks
+  *		rowSecParams			param-list if row-security has SubLink
   * ----------------
   */
  typedef struct ResultRelInfo
*************** typedef struct ResultRelInfo
*** 329,334 ****
--- 331,338 ----
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
  	ProjectionInfo *ri_projectReturning;
+ 	Node	   *ri_rowSecurity;
+ 	List	   *ri_rowSecParams;
  } ResultRelInfo;
  
  /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
new file mode 100644
index d4901ca..cb30110
*** a/src/include/nodes/nodeFuncs.h
--- b/src/include/nodes/nodeFuncs.h
***************
*** 24,29 ****
--- 24,30 ----
  #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
  #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
  #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+ #define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
  
  
  extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 9415e2c..7494b19
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum QuerySource
*** 31,37 ****
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
--- 31,38 ----
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
! 	QSRC_ROW_SECURITY,			/* added by row-security */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
*************** typedef struct Query
*** 112,118 ****
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
--- 113,121 ----
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 	int			sourceRelation;	/* rtable index of source relation for
! 								 * UPDATE/DELETE, if not identical with
! 								 * resultRelation; 0 for elsewhere */
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
*************** typedef struct RangeTblEntry
*** 727,732 ****
--- 730,740 ----
  	 */
  	Query	   *subquery;		/* the sub-query */
  	bool		security_barrier;		/* is from security_barrier view? */
+ 	Oid			rowsec_relid;	/* OID of the original relation, if this
+ 								 * sub-query originated from row-security
+ 								 * policy on the relation. Elsewhere, it
+ 								 * should be InvalidOid.
+ 								 */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
*************** typedef enum AlterTableType
*** 1266,1271 ****
--- 1274,1281 ----
  	AT_DropInherit,				/* NO INHERIT parent */
  	AT_AddOf,					/* OF <type_name> */
  	AT_DropOf,					/* NOT OF */
+ 	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+ 	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
  	AT_GenericOptions			/* OPTIONS (...) */
  } AlterTableType;
  
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index aa4f12c..c040c23
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct PlannedStmt
*** 67,72 ****
--- 67,74 ----
  	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
  
  	int			nParamExec;		/* number of PARAM_EXEC Params used */
+ 
+ 	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index c0a636b..ed01b65
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerGlobal
*** 98,103 ****
--- 98,105 ----
  	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
  
  	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+ 
+ 	Oid			planUserId;		/* User-Id to be assumed on this plan */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
*************** typedef struct AppendRelInfo
*** 1416,1421 ****
--- 1418,1427 ----
  	 */
  	Index		parent_relid;	/* RT index of append parent rel */
  	Index		child_relid;	/* RT index of append child rel */
+ 	Index		child_result;	/* RT index of append child rel's source,
+ 								 * if source of result relation is not
+ 								 * identical. Elsewhere, 0.
+ 								 */
  
  	/*
  	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index ...ff4dd9a
*** a/src/include/optimizer/rowsecurity.h
--- b/src/include/optimizer/rowsecurity.h
***************
*** 0 ****
--- 1,27 ----
+ /* -------------------------------------------------------------------------
+  *
+  * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+ 									 Relation relation, List *attnums);
+ extern void	apply_row_security_policy(PlannerInfo *root);
+ 
+ #endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
new file mode 100644
index bea3b07..3910853
*** 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/parser/parsetree.h b/src/include/parser/parsetree.h
new file mode 100644
index 9608cc6..b4e9df4
*** a/src/include/parser/parsetree.h
--- b/src/include/parser/parsetree.h
***************
*** 32,45 ****
  	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
  
  /*
-  *		getrelid
-  *
   *		Given the range index of a relation, return the corresponding
   *		relation OID.  Note that InvalidOid will be returned if the
   *		RTE is for a non-relation-type RTE.
   */
! #define getrelid(rangeindex,rangetable) \
! 	(rt_fetch(rangeindex, rangetable)->relid)
  
  /*
   * Given an RTE and an attribute number, return the appropriate
--- 32,42 ----
  	((RangeTblEntry *) list_nth(rangetable, (rangetable_index)-1))
  
  /*
   *		Given the range index of a relation, return the corresponding
   *		relation OID.  Note that InvalidOid will be returned if the
   *		RTE is for a non-relation-type RTE.
   */
! extern Oid	getrelid(Index rangeindex, List *rangetable);
  
  /*
   * Given an RTE and an attribute number, return the appropriate
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index f0604b0..6b6c364
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/parsenodes.h"
  
  extern List *QueryRewrite(Query *parsetree);
+ extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
  extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
  
  extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
new file mode 100644
index 72f8491..35819fa
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
*************** typedef struct CachedPlan
*** 128,133 ****
--- 128,135 ----
  	bool		is_oneshot;		/* is it a "oneshot" plan? */
  	bool		is_saved;		/* is CachedPlan in a long-lived context? */
  	bool		is_valid;		/* is the stmt_list currently valid? */
+ 	Oid			planUserId;		/* is user-id that is assumed on this cached
+ 								   plan, or InvalidOid if portable for anybody */
  	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
  								 * changes from this value */
  	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 589c9a8..4e4ac65
*** 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
*** 109,114 ****
--- 110,116 ----
  	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 */
  
  	/*
  	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index ...04591df
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
***************
*** 0 ****
--- 1,950 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ 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);
+ 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
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+ (4 rows)
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+   11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+   33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+   44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ERROR:  must be owner of relation document
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ ERROR:  must be owner of relation document
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 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'); -- failed
+ ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+ DETAIL:  Key (cid)=(33) is not present in table "category".
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ 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;
+ 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);
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+ SELECT * FROM t1;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ SELECT * FROM t1 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.b)
+          ->  Seq Scan on t1 t1_1
+                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)
+ 
+ -- reference to system column
+ SELECT oid, * FROM t1;
+  oid | a |  b  
+ -----+---+-----
+  102 | 2 | bbb
+  104 | 4 | ddd
+  201 | 1 | abc
+  203 | 3 | cde
+  301 | 1 | xxx
+  302 | 2 | yyy
+  303 | 3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- reference to whole-row reference
+ SELECT *,t1 FROM t1;
+  a |  b  |   t1    
+ ---+-----+---------
+  2 | bbb | (2,bbb)
+  4 | ddd | (4,ddd)
+  1 | abc | (1,abc)
+  3 | cde | (3,cde)
+  1 | xxx | (1,xxx)
+  2 | yyy | (2,yyy)
+  3 | zzz | (3,zzz)
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT *,t1 FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                ->  Seq Scan on t1 t1_1
+                      Filter: ((a % 2) = 0)
+          ->  Subquery Scan on t2
+                ->  Seq Scan on t2 t2_1
+                      Filter: ((a % 2) = 1)
+          ->  Seq Scan on t3
+ (9 rows)
+ 
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  Seq Scan on t1 t1_1
+                      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)
+ (12 rows)
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ 2	bbb
+ 4	ddd
+ COPY t1 TO stdout WITH OIDS;
+ 102	2	bbb
+ 104	4	ddd
+ COPY t2(c,b) TO stdout WITH OIDS;
+ 201	1.1	abc
+ 203	3.3	cde
+ COPY (SELECT * FROM t1) TO stdout;
+ 2	bbb
+ 4	ddd
+ 1	abc
+ 3	cde
+ 1	xxx
+ 2	yyy
+ 3	zzz
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ ERROR:  table "document" does not have OIDs
+ --
+ -- 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%';
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ ERROR:  infinite recursion detected for relation "s1"
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                      ->  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)
+ (11 rows)
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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
+                      ->  Subquery Scan on s2
+                            Filter: (s2.y ~~ '%af%'::text)
+                            ->  Seq Scan on s2 s2_1
+                                  Filter: ((x % 2) = 0)
+ (11 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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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 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
+  1 | abc
+  1 | xxx
+  2 | yyy
+ (4 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p1(2);
+                      QUERY PLAN                     
+ ----------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a <= 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a <= 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a <= 2)
+ (9 rows)
+ 
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ 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;
+ EXECUTE p2(2);
+  a |  b  
+ ---+-----
+  2 | bbb
+  2 | yyy
+ (2 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p2(2);
+                     QUERY PLAN                     
+ ---------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a = 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a = 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a = 2)
+ (9 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
+    ->  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)
+ 
+ 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
+    ->  Subquery Scan on t1_1
+          Filter: f_leak(t1_1.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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+  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 |             | 
+ (8 rows)
+ 
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+ DROP SCHEMA rls_regress_schema CASCADE;
+ NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index 432d39a..3b9123d
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
*************** SELECT relname, relhasindex
*** 120,125 ****
--- 120,126 ----
   pg_proc                 | t
   pg_range                | t
   pg_rewrite              | t
+  pg_rowsecurity          | t
   pg_seclabel             | t
   pg_shdepend             | t
   pg_shdescription        | t
*************** SELECT relname, relhasindex
*** 166,172 ****
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (155 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
--- 167,173 ----
   timetz_tbl              | f
   tinterval_tbl           | f
   varchar_tbl             | f
! (156 rows)
  
  --
  -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
new file mode 100644
index fd08e8d..367df00
*** 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
  
  # ----------
  # Another group of parallel tests
--- 83,89 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: privileges rowsecurity security_label collate matview lock
  
  # ----------
  # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 1ed059b..5883a64
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: delete
*** 93,98 ****
--- 93,99 ----
  test: namespace
  test: prepared_xacts
  test: privileges
+ test: rowsecurity
  test: security_label
  test: collate
  test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ...55d4aad
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
***************
*** 0 ****
--- 1,298 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ 
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ 
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 
+ -- 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'); -- failed
+ 
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM document;
+ SELECT * FROM category;
+ 
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ 
+ 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);
+ 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
+ \.
+ 
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ COPY t1 TO stdout WITH OIDS;
+ COPY t2(c,b) TO stdout WITH OIDS;
+ COPY (SELECT * FROM t1) TO stdout;
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ 
+ --
+ -- 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%';
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- OK
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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;
+ 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;
+ 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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-19 03:03           ` Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-20 13:47             ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 2 replies; 165+ messages in thread

From: Stephen Frost @ 2013-07-19 03:03 UTC (permalink / raw)
  To: Greg Smith <greg@2ndQuadrant.com>; +Cc: Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

* Greg Smith (greg@2ndQuadrant.com) wrote:
> On 7/18/13 7:57 PM, Karol Trzcionka wrote:
> >Current head 4cbe3ac3e86790d05c569de4585e5075a62a9b41 -> patch applies
> >correct (only change needed in parallel_schedule).
> >However it fails on own regression tests (other tests pass).
> 
> I got a rejected hunk in src/backend/nodes/nodeFuncs.c as well as
> that parallel_schedule issue.  Maybe you didn't get the nodeFuncs
> change but didn't notice that?  That might explain why the tests
> didn't work for you either.

The nodeFuncs.c hunk seems likely to have been impacted by the patch I
committed today (WITH CHECK OPTION), so I doubt that was the issue.

> Attached is an updated patch where I tried to only fix the two small
> hunks of bit rot.  I get "All 140 tests passed" here, on a Mac no
> less.

Thanks for updating the patch, I ran into the failed hunks too and
expected to have to deal with them. :)

> I did a brief code scan through the patch just to get a feel for how
> the feature is put together, and what you'd need to know for a
> deeper review.  

That would be extremely helpful..  Wasn't there a wiki page about this
feature which might also help?  Bigger question- is it correct for the
latest version of the patch?

> (I'm trying to get customer time approved to work on
> this a lot more)  The code was easier to follow than I expected.
> The way it completely avoids even getting into the security label
> integration yet seems like a successful design partitioning.  This
> isn't nearly as scary as the SEPostgres patches.  There are some
> useful looking utility functions that dump information about what's
> going on too.
> 
> The bulk of the complexity is how the feature modifies query nodes
> to restrict what rows come through them.  Some familiarity with that
> part of the code is what you'd need to take on reviewing this in
> detail. That and a week of time to spend trudging through it.  If
> anyone is looking for an educational challenge on query execution,
> marching through all of these changes to validate they work as
> expected would do that.

I'm hoping to find time this weekend to look into this patch myself, but
the weekend is also filling up with other activities, so we'll see.

	Thanks!

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130719030347.GV15510@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
@ 2013-07-19 03:09             ` Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Greg Smith @ 2013-07-19 03:09 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

On 7/18/13 11:03 PM, Stephen Frost wrote:
> Wasn't there a wiki page about this
> feature which might also help?  Bigger question- is it correct for the
> latest version of the patch?

https://wiki.postgresql.org/wiki/RLS has accumulated a lot of older 
debris that may or may not be useful here.

This useful piece was just presented at PGCon: 
http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf
That is very up to date intro to the big picture issues.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-19 10:41               ` Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-19 15:00                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 2 replies; 165+ messages in thread

From: Dean Rasheed @ 2013-07-19 10:41 UTC (permalink / raw)
  To: Greg Smith <greg@2ndquadrant.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

On 19 July 2013 04:09, Greg Smith <greg@2ndquadrant.com> wrote:
> On 7/18/13 11:03 PM, Stephen Frost wrote:
>>
>> Wasn't there a wiki page about this
>> feature which might also help?  Bigger question- is it correct for the
>> latest version of the patch?
>
>
> https://wiki.postgresql.org/wiki/RLS has accumulated a lot of older debris
> that may or may not be useful here.
>
> This useful piece was just presented at PGCon:
> http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf
> That is very up to date intro to the big picture issues.
>

Hi,

I took a look at this patch too. I didn't read all the code in detail,
but there was one area I wondered about --- is it still necessary to
add the new field rowsec_relid to RangeTblEntry?

As far as I can see, it is only used in the new function getrelid()
which replaces the old macro of the same name, so that it can work if
it is passed the index of the sourceRelation subquery RTE instead of
the resultRelation. This seems to be encoding a specific assumption
that a subquery RTE is always going to come from row-level security
expansion.

Is it the case that this can only happen from expand_targetlist(), in
which case why not pass both source_relation and result_relation to
expand_targetlist(), which I think will make that code neater. As it
stands, what expand_targetlist() sees as result_relation is actually
source_relation, which getrelid() then handles specially. Logically I
think expand_targetlist() should pass the actual result_relation to
getrelid(), opening the target table, and then pass source_relation to
makeVar() when building new TLEs.

With that change to expand_targetlist(), the change to getrelid() may
be unnecessary, and then also the new rowsec_relid field in
RangeTblEntry may not be needed.

Regards,
Dean


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2013-07-19 15:00                 ` Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 0 replies; 165+ messages in thread

From: Tom Lane @ 2013-07-19 15:00 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Greg Smith <greg@2ndquadrant.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

Dean Rasheed <dean.a.rasheed@gmail.com> writes:
> I took a look at this patch too. I didn't read all the code in detail,
> but there was one area I wondered about --- is it still necessary to
> add the new field rowsec_relid to RangeTblEntry?

> As far as I can see, it is only used in the new function getrelid()
> which replaces the old macro of the same name, so that it can work if
> it is passed the index of the sourceRelation subquery RTE instead of
> the resultRelation. This seems to be encoding a specific assumption
> that a subquery RTE is always going to come from row-level security
> expansion.

I haven't read the patch at all, but I would opine that anything that's
changing the behavior of getrelid() is broken by definition.  Something
is being done at the wrong level of abstraction if you think that you
need to do that.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2013-07-20 14:08                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-07-20 14:08 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Greg Smith <greg@2ndquadrant.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

2013/7/19 Dean Rasheed <dean.a.rasheed@gmail.com>:
> On 19 July 2013 04:09, Greg Smith <greg@2ndquadrant.com> wrote:
>> On 7/18/13 11:03 PM, Stephen Frost wrote:
>>>
>>> Wasn't there a wiki page about this
>>> feature which might also help?  Bigger question- is it correct for the
>>> latest version of the patch?
>>
>>
>> https://wiki.postgresql.org/wiki/RLS has accumulated a lot of older debris
>> that may or may not be useful here.
>>
>> This useful piece was just presented at PGCon:
>> http://www.pgcon.org/2013/schedule/attachments/273_PGcon2013-kaigai-row-level-security.pdf
>> That is very up to date intro to the big picture issues.
>>
>
> Hi,
>
> I took a look at this patch too. I didn't read all the code in detail,
> but there was one area I wondered about --- is it still necessary to
> add the new field rowsec_relid to RangeTblEntry?
>
> As far as I can see, it is only used in the new function getrelid()
> which replaces the old macro of the same name, so that it can work if
> it is passed the index of the sourceRelation subquery RTE instead of
> the resultRelation. This seems to be encoding a specific assumption
> that a subquery RTE is always going to come from row-level security
> expansion.
>
> Is it the case that this can only happen from expand_targetlist(), in
> which case why not pass both source_relation and result_relation to
> expand_targetlist(), which I think will make that code neater. As it
> stands, what expand_targetlist() sees as result_relation is actually
> source_relation, which getrelid() then handles specially. Logically I
> think expand_targetlist() should pass the actual result_relation to
> getrelid(), opening the target table, and then pass source_relation to
> makeVar() when building new TLEs.
>
> With that change to expand_targetlist(), the change to getrelid() may
> be unnecessary, and then also the new rowsec_relid field in
> RangeTblEntry may not be needed.
>
Hmm. I didn't have this idea. It seems to me fair enough and kills
necessity to enhance RangeTblEntry and getrelid() indeed.
I try to fix up this implementation according to your suggestion.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-07-22 20:27                   ` Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 04:23                     ` Re: [v9.4] row level security Atri Sharma <atri.jiit@gmail.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Greg Smith @ 2013-07-22 20:27 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

On 7/20/13 10:08 AM, Kohei KaiGai wrote:

>> With that change to expand_targetlist(), the change to getrelid() may
>> be unnecessary, and then also the new rowsec_relid field in
>> RangeTblEntry may not be needed.
>>
> Hmm. I didn't have this idea. It seems to me fair enough and kills
> necessity to enhance RangeTblEntry and getrelid() indeed.
> I try to fix up this implementation according to your suggestion.

Great, there's one useful bit of feedback for you then, and that seems 
to address Tom's getrelid concern too.

For the active CommitFest, I don't see any place we can go with this 
right now except for "Returned with Feedback".  We really need more 
reviewers willing to put a significant amount of time into going through 
this code.

Anyone who would like to see RLS committed should consider how you can 
get resources to support a skilled PostgreSQL reviewer spending time on 
it.  (This is a bit much to expect new reviewers to chew on usefully) 
I've been working on that here, but I don't have anything I can publicly 
commit to yet.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-23 04:23                     ` Atri Sharma <atri.jiit@gmail.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Atri Sharma @ 2013-07-23 04:23 UTC (permalink / raw)
  To: Greg Smith <greg@2ndquadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

> (This is a bit much to expect new reviewers to chew on usefully) I've been
> working on that here, but I don't have anything I can publicly commit to
> yet.
>

True that.

I spent some time on it, but couldn't come up with anything useful.
Mike's extensive testing seems good enough for me,  though.

One thing I was a bit concerned about (I don't know if it has been
resolved already, or if it isn't a concern) is that there was an issue
about a security part being visible in the syntax (or something like
that, my memory isn't too good today morning).

Regards,

Atri


Regards,

Atri
l'apprenant


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-23 16:10                     ` Josh Berkus <josh@agliodbs.com>
  2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Josh Berkus @ 2013-07-23 16:10 UTC (permalink / raw)
  To: Greg Smith <greg@2ndQuadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

On 07/22/2013 01:27 PM, Greg Smith wrote:
> 
> Anyone who would like to see RLS committed should consider how you can
> get resources to support a skilled PostgreSQL reviewer spending time on
> it.  (This is a bit much to expect new reviewers to chew on usefully)
> I've been working on that here, but I don't have anything I can publicly
> commit to yet.

Apparently it's a little much for experienced reviewers to chew on, too,
since I've been trying to get someone to review it since the beginning
of the Commitfest.

While I understand the call for "resources", this is a bit unfair to
KaiGai, who has put in his time reviewing other people's patches.

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-07-23 17:16                       ` Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 18:30                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Greg Smith @ 2013-07-23 17:16 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

On 7/23/13 12:10 PM, Josh Berkus wrote:
> Apparently it's a little much for experienced reviewers to chew on, too,
> since I've been trying to get someone to review it since the beginning
> of the Commitfest.

It's more than the available experienced reviewers are willing to chew 
on fully as volunteers.  The reward for spending review time is pretty 
low right now.

> While I understand the call for "resources", this is a bit unfair to
> KaiGai, who has put in his time reviewing other people's patches.

If you read Dean Rasheed's comments, it's obvious he put a useful amount 
of work into his review suggestions.  It is not the case here that 
KaiGai worked on a review and got nothing in return.  Unfortunately that 
has happened to this particular patch before, but the community did a 
little better this time.

The goal of the CF is usually to reach one of these outcomes for every 
submission:

-The submission is ready for commit
-The submission needs improvement in X

Review here went far enough to identify an X to be improved.  It was a 
big enough issue that a rewrite is needed, commit at this time isn't 
possible, and now KaiGai has something we hope is productive he can 
continue working on.  That's all we can really promise here--that if we 
say something isn't ready for commit yet, that there's some feedback as 
to why.

I would have preferred to see multiple X issues identified here, given 
that we know there has to be more than just the one in a submission of 
this size.  The rough fairness promises of the CommitFest seem satisfied 
to me though.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-23 18:30                         ` Josh Berkus <josh@agliodbs.com>
  2013-07-23 22:34                           ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-05 12:35                           ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
  0 siblings, 2 replies; 165+ messages in thread

From: Josh Berkus @ 2013-07-23 18:30 UTC (permalink / raw)
  To: Greg Smith <greg@2ndQuadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

Greg,

> It's more than the available experienced reviewers are willing to chew
> on fully as volunteers.  The reward for spending review time is pretty
> low right now.

Short of paying for review time, I don't think we have another solution
for getting the "big patches" reviewed, except to rely on the major
contributors who are paid full-time to hack Postgres.   You know as well
as me that, as consultants, we can get clients to pay for 10% extra time
for review in the course of developing a feature, but the kind of time
which patches like Row Security, Changesets, or other "big patches" need
nobody is going to pay for on a contract basis.  And nobody who is doing
this in their "spare time" has that kind of block.

So I don't think there's any good solution for the "big patches".

I do think our project could do much more to recruit reviewers for the
small-medium patches, to take workload off the core contributors in
general.  Historically, however, this project (and the contributors on
this list) has made material decisions not to encourage or recruit new
people as reviewers, and has repeatedly stated that reviewers are not
important.  Until that changes, we are not going to get more reviewers
(and I'm not going to have much sympathy for existing contributors who
say they have no time for review).

If we want more reviewers and people spending more time on review, then
we need to give reviewers the *same* respect and the *same* rewards that
feature contributors get.  Not something else, the exact same.

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 18:30                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-07-23 22:34                           ` Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 22:48                             ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Greg Smith @ 2013-07-23 22:34 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: pgsql-hackers

On 7/23/13 2:30 PM, Josh Berkus wrote:

> You know as well as me that, as consultants, we can get clients to pay for 10% extra time
> for review in the course of developing a feature

Before this number gets quoted anywhere, I think it's on the low side. 
I've spent a good bit of time measuring how much time it takes to do a 
fair offsetting review--one where you put as much time in as it takes to 
review your submission--and I keep getting numbers more in the 20 to 25% 
range.  The work involved to do a high quality review takes a while.

I happen to think the review structure is one of the most important 
components to PostgreSQL release quality.  It used to be a single level 
review with just the committers, now it's a two level structure.  The 
reason the Postgres code is so good isn't that the submitted development 
is inherently any better than other projects.  There's plenty of bogus 
material submitted here.  It's the high standards for review and commit 
that are the key filter.  The importance of the process to the result 
isn't weighed as heavily as I think it should be.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 18:30                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-07-23 22:34                           ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-07-23 22:48                             ` Josh Berkus <josh@agliodbs.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Josh Berkus @ 2013-07-23 22:48 UTC (permalink / raw)
  To: Greg Smith <greg@2ndQuadrant.com>; +Cc: pgsql-hackers

On 07/23/2013 03:34 PM, Greg Smith wrote:

> I happen to think the review structure is one of the most important
> components to PostgreSQL release quality.  It used to be a single level
> review with just the committers, now it's a two level structure.  The
> reason the Postgres code is so good isn't that the submitted development
> is inherently any better than other projects.  There's plenty of bogus
> material submitted here.  It's the high standards for review and commit
> that are the key filter.  The importance of the process to the result
> isn't weighed as heavily as I think it should be.

I think we're in violent agreement here.  Now, we just need to convince
everyone else on this list.

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-23 18:30                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-05 12:35                           ` Bruce Momjian <bruce@momjian.us>
  1 sibling, 0 replies; 165+ messages in thread

From: Bruce Momjian @ 2013-08-05 12:35 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Greg Smith <greg@2ndQuadrant.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Dean Rasheed <dean.a.rasheed@gmail.com>; Stephen Frost <sfrost@snowman.net>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

On Tue, Jul 23, 2013 at 11:30:14AM -0700, Josh Berkus wrote:
> Greg,
> 
> > It's more than the available experienced reviewers are willing to chew
> > on fully as volunteers.  The reward for spending review time is pretty
> > low right now.
> 
> Short of paying for review time, I don't think we have another solution
> for getting the "big patches" reviewed, except to rely on the major
> contributors who are paid full-time to hack Postgres.   You know as well
> as me that, as consultants, we can get clients to pay for 10% extra time
> for review in the course of developing a feature, but the kind of time
> which patches like Row Security, Changesets, or other "big patches" need
> nobody is going to pay for on a contract basis.  And nobody who is doing
> this in their "spare time" has that kind of block.
> 
> So I don't think there's any good solution for the "big patches".

Let me echo Josh's comments above --- in the early years, we had trouble
creating new features that required more than 1-2 weekends of
development.  We now have enough full-time developers that this is not a
problem, but now it seems features requiring more than a weekend to
_review_ are a problem, so full-time folks are again required here.

-- 
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + It's impossible for everything to be true. +


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-27 21:37                   ` Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 11:56                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 2 replies; 165+ messages in thread

From: Greg Smith @ 2013-08-27 21:37 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: pgsql-hackers

On 7/20/13 10:08 AM, Kohei KaiGai wrote:
> Hmm. I didn't have this idea. It seems to me fair enough and kills
> necessity to enhance RangeTblEntry and getrelid() indeed.
> I try to fix up this implementation according to your suggestion.

How is that going?  I'm going to do a serious review of this myself over 
the next few weeks.  I have a good chunk of time set aside for it as 
part of a larger project.  I'm hoping to get more people here involved 
in that effort too, starting in the November CF if that works out.

I've been trying to catch up with your larger plan for this feature for 
9.4.  You made this comment earlier:

 > Also, I'd like to have discussion for this feature in earlier half of
 > v9.4 to keep time for the remaining features, such as check on
 > writer-side, integration with selinux, and so on

Is any of that code around yet?  I see that you have split your 
submissions so that a smaller program can be reviewed today.  I'd like 
to start taking a look at the next step too though.  For the project I'm 
starting to work on here, getting the integration with labeling also 
done is a very important thing to target for 9.4.  It would be nice to 
see how that fits together today, even if the code for it isn't being 
reviewed heavily yet.

I don't quite understand yet what's missing on the writer side.  If you 
could help explain what's missing there, I would like to read about that.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-08-28 06:45                     ` Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Oleg Bartunov @ 2013-08-28 06:45 UTC (permalink / raw)
  To: Greg Smith <greg@2ndquadrant.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

btw, there is serious problem with row-level security and constraints. For
example, user with low security level could use unique constraint to know
about existence of a row with higher security.  I don't know, what is the
best practice to avoid this.


On Wed, Aug 28, 2013 at 1:37 AM, Greg Smith <greg@2ndquadrant.com> wrote:

> On 7/20/13 10:08 AM, Kohei KaiGai wrote:
>
>> Hmm. I didn't have this idea. It seems to me fair enough and kills
>> necessity to enhance RangeTblEntry and getrelid() indeed.
>> I try to fix up this implementation according to your suggestion.
>>
>
> How is that going?  I'm going to do a serious review of this myself over
> the next few weeks.  I have a good chunk of time set aside for it as part
> of a larger project.  I'm hoping to get more people here involved in that
> effort too, starting in the November CF if that works out.
>
> I've been trying to catch up with your larger plan for this feature for
> 9.4.  You made this comment earlier:
>
> > Also, I'd like to have discussion for this feature in earlier half of
> > v9.4 to keep time for the remaining features, such as check on
> > writer-side, integration with selinux, and so on
>
> Is any of that code around yet?  I see that you have split your
> submissions so that a smaller program can be reviewed today.  I'd like to
> start taking a look at the next step too though.  For the project I'm
> starting to work on here, getting the integration with labeling also done
> is a very important thing to target for 9.4.  It would be nice to see how
> that fits together today, even if the code for it isn't being reviewed
> heavily yet.
>
> I don't quite understand yet what's missing on the writer side.  If you
> could help explain what's missing there, I would like to read about that.
>
> --
> Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
> PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com
>
>
> --
> Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/**mailpref/pgsql-hackers<http://www.postgresql.org/mailpref/pgsql-hacke...;
>

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
@ 2013-08-28 12:17                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 11:05                         ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-28 12:17 UTC (permalink / raw)
  To: obartunov@gmail.com; +Cc: Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
> btw, there is serious problem with row-level security and constraints. For
> example, user with low security level could use unique constraint to know
> about existence of a row with higher security.  I don't know, what is the
> best practice to avoid this.
>
It is out of scope for this feature. We usually calls this type of information
leakage "covert channel"; that is not avoidable in principle.
However, its significance is minor, because attacker must know identical
data to be here, or must have proving for each possible values.
Its solution is simple. DBA should not use value to be confidential as unique
key. If needed, our recommendation is alternative key, instead of natural key,
because its value itself does not have worth.

I should add a note of caution onto the documentation according to
the previous consensus, however, I noticed it had gone from the sgml files
while I was unaware. So, let me add description on the documentation.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-29 11:05                         ` Oleg Bartunov <obartunov@gmail.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Oleg Bartunov @ 2013-08-29 11:05 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Any constraints could be "covert channel".


On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

> 2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
> > btw, there is serious problem with row-level security and constraints.
> For
> > example, user with low security level could use unique constraint to know
> > about existence of a row with higher security.  I don't know, what is the
> > best practice to avoid this.
> >
> It is out of scope for this feature. We usually calls this type of
> information
> leakage "covert channel"; that is not avoidable in principle.
> However, its significance is minor, because attacker must know identical
> data to be here, or must have proving for each possible values.
> Its solution is simple. DBA should not use value to be confidential as
> unique
> key. If needed, our recommendation is alternative key, instead of natural
> key,
> because its value itself does not have worth.
>
> I should add a note of caution onto the documentation according to
> the previous consensus, however, I noticed it had gone from the sgml files
> while I was unaware. So, let me add description on the documentation.
>
> Thanks,
> --
> KaiGai Kohei <kaigai@kaigai.gr.jp>
>

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-29 12:09                         ` Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:05                           ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 12:41                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  1 sibling, 3 replies; 165+ messages in thread

From: Alexander Korotkov @ 2013-08-29 12:09 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

> 2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
> > btw, there is serious problem with row-level security and constraints.
> For
> > example, user with low security level could use unique constraint to know
> > about existence of a row with higher security.  I don't know, what is the
> > best practice to avoid this.
> >
> It is out of scope for this feature. We usually calls this type of
> information
> leakage "covert channel"; that is not avoidable in principle.
> However, its significance is minor, because attacker must know identical
> data to be here, or must have proving for each possible values.
> Its solution is simple. DBA should not use value to be confidential as
> unique
> key. If needed, our recommendation is alternative key, instead of natural
> key,
> because its value itself does not have worth.
>
> I should add a note of caution onto the documentation according to
> the previous consensus, however, I noticed it had gone from the sgml files
> while I was unaware. So, let me add description on the documentation.


I think there is another "covert channel" much more serious than
constrains. You can gather information about hidden data by reading query
plans.

CREATE TABLE test (id SERIAL PRIMARY KEY, value INTEGER);
INSERT INTO test (value) VALUES (1234), (4321), (2356), (6542);
ALTER TABLE test SET ROW SECURITY FOR ALL TO (id % 2 = 1);
CREATE INDEX test_idx ON test (value);

User sees only 1 and 3 rows:

postgres=> select * from test;
 id | value
----+-------
  1 |  1234
  3 |  2356
(2 rows)

But user can probe values column using explain command.

postgres=> set enable_seqscan = off;
 SET
postgres=> explain analyze select * from test where value between 1111 and
3333;
                                                  QUERY PLAN
---------------------------------------------------------------------------------------------------------------
 Index Scan using test_idx on test  (cost=0.13..8.16 rows=1 width=8)
(actual time=0.021..0.024 rows=2 loops=1)
   Index Cond: ((value >= 1111) AND (value <= 3333))
   Filter: ((id % 2) = 1)
 Total runtime: 0.056 ms
(4 rows)

postgres=> explain analyze select * from test where value between 1111 and
5555;
                                                  QUERY PLAN
---------------------------------------------------------------------------------------------------------------
 Index Scan using test_idx on test  (cost=0.13..8.16 rows=1 width=8)
(actual time=0.020..0.024 rows=2 loops=1)
   Index Cond: ((value >= 1111) AND (value <= 5555))
   Filter: ((id % 2) = 1)
   Rows Removed by Filter: 1
 Total runtime: 0.057 ms
(5 rows)

In given example user can realize that there is a hidden value in index
between 3334 and 5555. Using dichotomy he can find exact value.
I didn't find if there was discussion about it. This example is only my
first idea about using plans for probing hidden values. Probably, there are
some other ways to do it.
I don't think we can say that indexed data is not sensitive for leakage.
Prohibiting push down of all operators which could be used for such kind of
attacks also doesn't seem acceptable for me because of huge impact to
performance. Another option I see is to hide some sensitive parts of plan
from unprivileged user. There is still a room for timing attack, but it
doesn't seem to be feasible in practice to apply.

------
With best regards,
Alexander Korotkov.

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
@ 2013-08-29 14:05                           ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-29 17:27                             ` Re: [v9.4] row level security David Fetter <david@fetter.org>
  2 siblings, 1 reply; 165+ messages in thread

From: Tom Lane @ 2013-08-29 14:05 UTC (permalink / raw)
  To: Alexander Korotkov <aekorotkov@gmail.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Alexander Korotkov <aekorotkov@gmail.com> writes:
> On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> It is out of scope for this feature. We usually calls this type of
>> information leakage "covert channel"; that is not avoidable in principle.

> I think there is another "covert channel" much more serious than
> constrains. You can gather information about hidden data by reading query
> plans.

I'm not convinced by this argument that covert channels are "out of
scope".  That would be a fine justification for, say, a thesis topic.
However, what we're talking about here is a real-world feature that will
be of no real-world use if it can't stand up against rather obvious attack
techniques.  I'm not interested in carrying the maintenance and runtime
overhead of a feature that's only of academic value.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:05                           ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-29 17:27                             ` David Fetter <david@fetter.org>
  2013-08-30 10:13                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: David Fetter @ 2013-08-29 17:27 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Alexander Korotkov <aekorotkov@gmail.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Thu, Aug 29, 2013 at 10:05:14AM -0400, Tom Lane wrote:
> Alexander Korotkov <aekorotkov@gmail.com> writes:
> > On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> >> It is out of scope for this feature. We usually calls this type
> >> of information leakage "covert channel"; that is not avoidable in
> >> principle.
> 
> > I think there is another "covert channel" much more serious than
> > constrains. You can gather information about hidden data by
> > reading query plans.
> 
> I'm not convinced by this argument that covert channels are "out of
> scope".  That would be a fine justification for, say, a thesis
> topic.  However, what we're talking about here is a real-world
> feature that will be of no real-world use if it can't stand up
> against rather obvious attack techniques.  I'm not interested in
> carrying the maintenance and runtime overhead of a feature that's
> only of academic value.

Looking at the real-world perspective, what covert channels do our
competitors in the space currently claim to do anything about?

This would represent the bar we need to clear at least as far as
documenting what we do (do the access constraint before anything else,
e.g.) or why we don't do things (disabling EXPLAIN, e.g.).

Cheers,
David.
-- 
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778  AIM: dfetter666  Yahoo!: dfetter
Skype: davidfetter      XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:05                           ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-29 17:27                             ` Re: [v9.4] row level security David Fetter <david@fetter.org>
@ 2013-08-30 10:13                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-30 10:13 UTC (permalink / raw)
  To: David Fetter <david@fetter.org>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/29 David Fetter <david@fetter.org>:
> On Thu, Aug 29, 2013 at 10:05:14AM -0400, Tom Lane wrote:
>> Alexander Korotkov <aekorotkov@gmail.com> writes:
>> > On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> >> It is out of scope for this feature. We usually calls this type
>> >> of information leakage "covert channel"; that is not avoidable in
>> >> principle.
>>
>> > I think there is another "covert channel" much more serious than
>> > constrains. You can gather information about hidden data by
>> > reading query plans.
>>
>> I'm not convinced by this argument that covert channels are "out of
>> scope".  That would be a fine justification for, say, a thesis
>> topic.  However, what we're talking about here is a real-world
>> feature that will be of no real-world use if it can't stand up
>> against rather obvious attack techniques.  I'm not interested in
>> carrying the maintenance and runtime overhead of a feature that's
>> only of academic value.
>
> Looking at the real-world perspective, what covert channels do our
> competitors in the space currently claim to do anything about?
>
I'm not sure whether minor dbms that is designed for extreme secure
environment already got certified. (If they have such functionality,
they should take certification for promotion.)

Oracle lists some of their certified products:
http://www.oracle.com/technetwork/topics/security/security-evaluations-099357.html

However, these are based on protection profile for basic robustness that is
designed for environment where we don't care about covert channel.

> This would represent the bar we need to clear at least as far as
> documenting what we do (do the access constraint before anything else,
> e.g.) or why we don't do things (disabling EXPLAIN, e.g.).
>
+1. I'd like to add description about this scenario.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
@ 2013-08-29 14:14                           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-29 14:14 UTC (permalink / raw)
  To: Alexander Korotkov <aekorotkov@gmail.com>; +Cc: Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/29 Alexander Korotkov <aekorotkov@gmail.com>:
> On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>>
>> 2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
>> > btw, there is serious problem with row-level security and constraints.
>> > For
>> > example, user with low security level could use unique constraint to
>> > know
>> > about existence of a row with higher security.  I don't know, what is
>> > the
>> > best practice to avoid this.
>> >
>> It is out of scope for this feature. We usually calls this type of
>> information
>> leakage "covert channel"; that is not avoidable in principle.
>> However, its significance is minor, because attacker must know identical
>> data to be here, or must have proving for each possible values.
>> Its solution is simple. DBA should not use value to be confidential as
>> unique
>> key. If needed, our recommendation is alternative key, instead of natural
>> key,
>> because its value itself does not have worth.
>>
>> I should add a note of caution onto the documentation according to
>> the previous consensus, however, I noticed it had gone from the sgml files
>> while I was unaware. So, let me add description on the documentation.
>
>
> I think there is another "covert channel" much more serious than constrains.
> You can gather information about hidden data by reading query plans.
>
> CREATE TABLE test (id SERIAL PRIMARY KEY, value INTEGER);
> INSERT INTO test (value) VALUES (1234), (4321), (2356), (6542);
> ALTER TABLE test SET ROW SECURITY FOR ALL TO (id % 2 = 1);
> CREATE INDEX test_idx ON test (value);
>
> User sees only 1 and 3 rows:
>
> postgres=> select * from test;
>  id | value
> ----+-------
>   1 |  1234
>   3 |  2356
> (2 rows)
>
> But user can probe values column using explain command.
>
> postgres=> set enable_seqscan = off;
> SET
> postgres=> explain analyze select * from test where value between 1111 and
> 3333;
>                                                   QUERY PLAN
> ---------------------------------------------------------------------------------------------------------------
>  Index Scan using test_idx on test  (cost=0.13..8.16 rows=1 width=8) (actual
> time=0.021..0.024 rows=2 loops=1)
>    Index Cond: ((value >= 1111) AND (value <= 3333))
>    Filter: ((id % 2) = 1)
>  Total runtime: 0.056 ms
> (4 rows)
>
> postgres=> explain analyze select * from test where value between 1111 and
> 5555;
>                                                   QUERY PLAN
> ---------------------------------------------------------------------------------------------------------------
>  Index Scan using test_idx on test  (cost=0.13..8.16 rows=1 width=8) (actual
> time=0.020..0.024 rows=2 loops=1)
>    Index Cond: ((value >= 1111) AND (value <= 5555))
>    Filter: ((id % 2) = 1)
>    Rows Removed by Filter: 1
>  Total runtime: 0.057 ms
> (5 rows)
>
> In given example user can realize that there is a hidden value in index
> between 3334 and 5555. Using dichotomy he can find exact value.
> I didn't find if there was discussion about it. This example is only my
> first idea about using plans for probing hidden values. Probably, there are
> some other ways to do it.
>
Hmm. It is a new scenario for me, even though its basis is similar to
the scenario that abuses constraints because it still does not leak
the hidden value itself and it requires "estimation" by human.

If we tackle this issue, an easy solution is to hide "rows removed by
filter" output if plan is underlying sub-query plan that was constructed
by row-level security feature.

> I don't think we can say that indexed data is not sensitive for leakage.
> Prohibiting push down of all operators which could be used for such kind of
> attacks also doesn't seem acceptable for me because of huge impact to
> performance. Another option I see is to hide some sensitive parts of plan
> from unprivileged user. There is still a room for timing attack, but it
> doesn't seem to be feasible in practice to apply.
>
A principle of this row-level security feature is, it prohibits to
leak invisible
datum itself, but might allow users to expect existence of records with
a particular value. In fact, we never push down function that may leak
the given argument, that does not have leakproof attribute, even if it can
be utilized for index-scan.
My opinion is, we should deal with it is "a limitation" of this feature, as
long as it does not expose the raw data to be hidden. Estimation takes
time to carry out much hidden data via covert channel, thus traditional
secure operating system specification with MAC implementation says
its degree of threat is not significant as long as bandwidth of covert
channel is not so much. I think it is a reasonable standpoint.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-29 14:23                             ` ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: ktm@rice.edu @ 2013-08-29 14:23 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Thu, Aug 29, 2013 at 04:14:53PM +0200, Kohei KaiGai wrote:
> 2013/8/29 Alexander Korotkov <aekorotkov@gmail.com>:
> > On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> >>
> >> 2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
> >> > btw, there is serious problem with row-level security and constraints.
> >> > For
> >> > example, user with low security level could use unique constraint to
> >> > know
> >> > about existence of a row with higher security.  I don't know, what is
> >> > the
> >> > best practice to avoid this.
> >> >
...
> >
> A principle of this row-level security feature is, it prohibits to
> leak invisible
> datum itself, but might allow users to expect existence of records with
> a particular value. In fact, we never push down function that may leak
> the given argument, that does not have leakproof attribute, even if it can
> be utilized for index-scan.
> My opinion is, we should deal with it is "a limitation" of this feature, as
> long as it does not expose the raw data to be hidden. Estimation takes
> time to carry out much hidden data via covert channel, thus traditional
> secure operating system specification with MAC implementation says
> its degree of threat is not significant as long as bandwidth of covert
> channel is not so much. I think it is a reasonable standpoint.
> 
> Thanks,
> -- 
> KaiGai Kohei <kaigai@kaigai.gr.jp>
> 

Okay, given that argument, how would you monitor such attempts to access
data through the covert channel and shut it down?

Regards,
Ken


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
@ 2013-08-29 14:35                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:09                                 ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  0 siblings, 2 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-29 14:35 UTC (permalink / raw)
  To: ktm@rice.edu <ktm@rice.edu>; +Cc: Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/29 ktm@rice.edu <ktm@rice.edu>:
> On Thu, Aug 29, 2013 at 04:14:53PM +0200, Kohei KaiGai wrote:
>> 2013/8/29 Alexander Korotkov <aekorotkov@gmail.com>:
>> > On Wed, Aug 28, 2013 at 4:17 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> >>
>> >> 2013/8/28 Oleg Bartunov <obartunov@gmail.com>:
>> >> > btw, there is serious problem with row-level security and constraints.
>> >> > For
>> >> > example, user with low security level could use unique constraint to
>> >> > know
>> >> > about existence of a row with higher security.  I don't know, what is
>> >> > the
>> >> > best practice to avoid this.
>> >> >
> ...
>> >
>> A principle of this row-level security feature is, it prohibits to
>> leak invisible
>> datum itself, but might allow users to expect existence of records with
>> a particular value. In fact, we never push down function that may leak
>> the given argument, that does not have leakproof attribute, even if it can
>> be utilized for index-scan.
>> My opinion is, we should deal with it is "a limitation" of this feature, as
>> long as it does not expose the raw data to be hidden. Estimation takes
>> time to carry out much hidden data via covert channel, thus traditional
>> secure operating system specification with MAC implementation says
>> its degree of threat is not significant as long as bandwidth of covert
>> channel is not so much. I think it is a reasonable standpoint.
>>
>> Thanks,
>> --
>> KaiGai Kohei <kaigai@kaigai.gr.jp>
>>
>
> Okay, given that argument, how would you monitor such attempts to access
> data through the covert channel and shut it down?
>
Although I didn't touch this task by myself, my senior colleague said that we
calculated some possible bandwidth of leakage as an evident when we ship
supercomputer system with MAC feature for customer who worry about security.
I'm not sure how to measure it. However, if we assume a human can run up to
5 query per seconds, he needs 2-3 seconds to identify a particular integer value
less than 10000, it means bandwidth of this covert channel is less than 5bps.
I'm not sure whether enterprise-grade dbms has to care about these amount
of covert channel.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-29 16:59                                 ` Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:18                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 2 replies; 165+ messages in thread

From: Josh Berkus @ 2013-08-29 16:59 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Kaigai,

> Although I didn't touch this task by myself, my senior colleague said that we
> calculated some possible bandwidth of leakage as an evident when we ship
> supercomputer system with MAC feature for customer who worry about security.
> I'm not sure how to measure it. However, if we assume a human can run up to
> 5 query per seconds, he needs 2-3 seconds to identify a particular integer value
> less than 10000, it means bandwidth of this covert channel is less than 5bps.
> I'm not sure whether enterprise-grade dbms has to care about these amount
> of covert channel.

Why are you assuming a human needs to do it?  Given the explain vector,
I could write a rather simple python or perl script which would find
values by EXPLAIN leakage, at 1000 explain plans per minute.

It's one thing to day "we can't solve this covert channel issue right
now in this patch", but saying "we don't plan to solve it at all" is
likely to doom the patch.

I'm not sure what the solution would be, exactly.  Deny permission for
EXPLAIN on certain tables?

Surely someone in the security community has discussed this?

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-29 17:18                                   ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-29 17:22                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:28                                     ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 2 replies; 165+ messages in thread

From: Tom Lane @ 2013-08-29 17:18 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Josh Berkus <josh@agliodbs.com> writes:
> It's one thing to day "we can't solve this covert channel issue right
> now in this patch", but saying "we don't plan to solve it at all" is
> likely to doom the patch.

> I'm not sure what the solution would be, exactly.  Deny permission for
> EXPLAIN on certain tables?

That would close only one covert channel.  Others were already pointed out
upthread, and I'll bet there are more ...

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:18                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-29 17:22                                     ` Josh Berkus <josh@agliodbs.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Josh Berkus @ 2013-08-29 17:22 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers


>> I'm not sure what the solution would be, exactly.  Deny permission for
>> EXPLAIN on certain tables?
> 
> That would close only one covert channel.  Others were already pointed out
> upthread, and I'll bet there are more ...

Mind you, fundamentally this is no different from allowing INSERT
permission on a table but denying SELECT, or denying SELECT on certain
columns.  In either case, covert channels for some data are available.

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:18                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-29 17:28                                     ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 10:20                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Tom Lane @ 2013-08-29 17:28 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Josh Berkus <josh@agliodbs.com> writes:
>> That would close only one covert channel.  Others were already pointed out
>> upthread, and I'll bet there are more ...

> Mind you, fundamentally this is no different from allowing INSERT
> permission on a table but denying SELECT, or denying SELECT on certain
> columns.  In either case, covert channels for some data are available.

Certainly.  But INSERT's purpose in life is not to prevent people from
inferring what data is in the table.  What we have to ask here is whether
a "row level security" feature that doesn't deal with these real-world
attack techniques is worth having.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-29 17:18                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-29 17:28                                     ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-30 10:20                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-30 10:20 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/29 Tom Lane <tgl@sss.pgh.pa.us>:
> Josh Berkus <josh@agliodbs.com> writes:
>>> That would close only one covert channel.  Others were already pointed out
>>> upthread, and I'll bet there are more ...
>
>> Mind you, fundamentally this is no different from allowing INSERT
>> permission on a table but denying SELECT, or denying SELECT on certain
>> columns.  In either case, covert channels for some data are available.
>
> Certainly.  But INSERT's purpose in life is not to prevent people from
> inferring what data is in the table.  What we have to ask here is whether
> a "row level security" feature that doesn't deal with these real-world
> attack techniques is worth having.
>
I think, we should clearly note that row-level security feature does not
have capability to control information leakage via covert channel but
very limited bandwidth, even though it control information leakage and
manipulation via main channel.
It depends on user's environment and expectation. If they need rdbms
with security feature for military grade, it is not recommendable.
However, it is a recommended solution for regular enterprise grade
environment. Anything depends on user's environment, threats and
worth of values to be protected.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-30 10:05                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-30 10:05 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/29 Josh Berkus <josh@agliodbs.com>:
> Kaigai,
>
>> Although I didn't touch this task by myself, my senior colleague said that we
>> calculated some possible bandwidth of leakage as an evident when we ship
>> supercomputer system with MAC feature for customer who worry about security.
>> I'm not sure how to measure it. However, if we assume a human can run up to
>> 5 query per seconds, he needs 2-3 seconds to identify a particular integer value
>> less than 10000, it means bandwidth of this covert channel is less than 5bps.
>> I'm not sure whether enterprise-grade dbms has to care about these amount
>> of covert channel.
>
> Why are you assuming a human needs to do it?  Given the explain vector,
> I could write a rather simple python or perl script which would find
> values by EXPLAIN leakage, at 1000 explain plans per minute.
>
> It's one thing to day "we can't solve this covert channel issue right
> now in this patch", but saying "we don't plan to solve it at all" is
> likely to doom the patch.
>
> I'm not sure what the solution would be, exactly.  Deny permission for
> EXPLAIN on certain tables?
>
> Surely someone in the security community has discussed this?
>
Security community considers covert channel is a hard to solve problem;
nearly impossible to eliminate.
Let's see the summary in wikipedia:
  http://en.wikipedia.org/wiki/Covert_channel

It does not require countermeasure of covert channels in middle or entry
class security evaluation; that is usually required for enterprise grade,
even though it is required for the product being designed for military
grade.
The reason why its priority is relatively lower, is that degree of threats
with information leakage via covert channel has limited bandwidth in
comparison to main channel.

I also follow this standpoint; that is enough reasonable between
functionality and its strictness under limited resources.
Even if we could close a certain channel, we never can all other
channels, like a signal by namespace contention on table creation
as covert channel. Also, I don't know major commercial dbms handles
these scenario well.

Of course, it should be described in the document for users not to
apply these security features onto the region that overs our capability.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-30 17:36                                     ` Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 2 replies; 165+ messages in thread

From: Josh Berkus @ 2013-08-30 17:36 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 08/30/2013 03:05 AM, Kohei KaiGai wrote:

>> Surely someone in the security community has discussed this?
>>
> Security community considers covert channel is a hard to solve problem;
> nearly impossible to eliminate.
> Let's see the summary in wikipedia:
>   http://en.wikipedia.org/wiki/Covert_channel

Well, in each of the cases covered in that article, the given technology
(OSI, TCP, etc.) takes specific provisions to limit the ability of
attackers to discover information via the covert channel.

However, we have yet to talk about taking any such provisions with
Postgres.  If we commit this patch, arguably we'll have a row-level
security feature which only protects data from well-behaved users, which
seems counterproductive.

So, arguments in favor of this patch:
a) it's as good as Oracle's security features, giving us "check-box
compliance".
b) it allows securing individual rows against attackers with limited
technical knowledge or limited database access, and could be very
hardened in combination with intelligent access control.
c) it is an improvement on techniques like Veil (is it)?
d) we plan to continue improving it and closing covert channels, or
limiting their bandwidth.

Arguments against:
m) covert channels are currently broad enough to make it trivially
circumventable (are they?)
n) overhead and code maintenance required is substantial

So, determinative questions:

1) are the security mechanisms supplied by this patch superior in some
way to approaches like Veil for multi-tenant applications?  (this is a
serious question, as multi-tenant applications are far less concerned
about covert channels)

2) do we plan to reduce the accessibility of data via covert channels
over successive releases?  How?

3) will accepting this patch allow our users in the Government space to
more freely adopt PostgreSQL?

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-30 19:27                                       ` Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  1 sibling, 1 reply; 165+ messages in thread

From: Stephen Frost @ 2013-08-30 19:27 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Josh,

* Josh Berkus (josh@agliodbs.com) wrote:
> On 08/30/2013 03:05 AM, Kohei KaiGai wrote:
> > Security community considers covert channel is a hard to solve problem;
> > nearly impossible to eliminate.

While impossible to eliminate, we should certainly consider cases like
this where we can do better and fix them.  RLS certainly brings another
level of consideration to the overall PG security environment by
requiring we think about security on a row level rather than just a
table or column level.

We have issues with covert channels even without RLS though and holding
up RLS because it doesn't fix all the covert channels isn't sensible.
Column-level privleges have a similar problem, where you can read the
default value for a column using the catalogs.  Perhaps the default
isn't sensetive (you'd certainly hope not), but it's still an issue.  It
wouldn't surprise me to find that there are ways to abuse a multi-column
index which includes both a column you can manipulate and one you don't
have access to read to determine something about the hidden column
(maybe you have access to the 2nd field in the index and you can
encourage an in-order index traversal and then look at filtered rows, or
just work out a way to do timing attacks to determine the btree depth).

> Well, in each of the cases covered in that article, the given technology
> (OSI, TCP, etc.) takes specific provisions to limit the ability of
> attackers to discover information via the covert channel.

The work we've done around secure views would lend credit to our
attempts at taking specific provisions as well; sadly, PG is slightly
more complicated than TCP.  We do what we can and we've got a great
community which will point out where we can do better- and we work on it
and improve over time.  Hell, when roles were first added we had a
*massive* security hole because we didn't check to make sure we weren't
overrunning the length of the GUC.  It was a mistake and we should have
done better, but that doesn't mean adding roles was the wrong decision.

> However, we have yet to talk about taking any such provisions with
> Postgres.  If we commit this patch, arguably we'll have a row-level
> security feature which only protects data from well-behaved users, which
> seems counterproductive.

I would argue both that we *have* been taking provisions to avoid
obvious and big covert channels, and that this patch adds value even
if it doesn't protect the system perfectly from malicious users.  We're
all certainly aware of the ability for an attacker to cause major
problems to a PG system if they can issue arbitrary SQL and our
permissions system doesn't do much to protect us.  A single query which
doesn't require any privileges could cause havok on the system (massive
on-disk temp file, which could be shared with pg_xlog causing the system
to PANIC, massive CPU load if they can execute multiple commands in
parallel...).  Not to mention the default installation of pl/pgsql and
anonymous functions.

I could see many a web app (things like LedgerSMB) which could benefit
from having more fine-grained in-database control because they already
authenticate to the database as the user and have a static or at least
controlled set of queries which they run.  Today, any of those kinds of
systems have to implement their own RLS (though sometimes it's done
through independent tables for each customer or similar, rather than as
conditionals added to queries).

> a) it's as good as Oracle's security features, giving us "check-box
> compliance".

I'd argue that this is definitely much more than 'check-box' compliance.

> b) it allows securing individual rows against attackers with limited
> technical knowledge or limited database access, and could be very
> hardened in combination with intelligent access control.
> c) it is an improvement on techniques like Veil (is it)?
> d) we plan to continue improving it and closing covert channels, or
> limiting their bandwidth.
> 
> Arguments against:
> m) covert channels are currently broad enough to make it trivially
> circumventable (are they?)

There are some which are and likely deserve to be fixed.  Do they all
need to be addressed before this patch goes in?  I'd argue 'no'.

> n) overhead and code maintenance required is substantial
> 
> So, determinative questions:
> 
> 1) are the security mechanisms supplied by this patch superior in some
> way to approaches like Veil for multi-tenant applications?  (this is a
> serious question, as multi-tenant applications are far less concerned
> about covert channels)

I'd argue 'yes' if just for the fact that it'd be simpler and easier to
use, both because it's in core and because you're using an actual
grammar instead of function calls- but this RLS does more than just
that, it's going to cause us to improve things that Veil probably can't
fix and simply ignores today.

> 2) do we plan to reduce the accessibility of data via covert channels
> over successive releases?  How?

By discovering them and fixing them as we go..?  I can't imagine there
being one massive patch which goes into a single major release that
fixes *all* of them- there's going to be ones we can't even imagine
today that we discover later.  Should we fix *all* of the ones that we
discover?  Probably not- it's simply not possible.  Should we fix the
ones that we can easily correct?  Of course.

> 3) will accepting this patch allow our users in the Government space to
> more freely adopt PostgreSQL?

There's two parts to this.  On the one hand, the 'check-box' would be
filled, which by itself would make it easier (at least based on my
experience w/ the US gov't, ymmv), but also because it would require
*less work* to build a new application on PG which needed RLS.  You can
already do it today, but you have to bake that into the cost of the
implementation of the overall application and accept the limitations
which come with it- trivally gotten around once you get a direct
connection to PG.  Would this be perfect?  No, but it'd be quite a bit
better.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130830192729.GO2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
@ 2013-08-30 19:43                                         ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 20:24                                           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 3 replies; 165+ messages in thread

From: Tom Lane @ 2013-08-30 19:43 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Stephen Frost <sfrost@snowman.net> writes:
> We have issues with covert channels even without RLS though and holding
> up RLS because it doesn't fix all the covert channels isn't sensible.

I think it's entirely sensible to question whether we should reject (not
"hold up") RLS if it has major covert-channel problems.

The scenario I'm worried about is where somebody says "hey, Postgres has
RLS now, I can rely on that to hide my sooper sekrit data from other users
in the same database", and later they have a security breach through some
covert-channel attack.  Are they going to blame themselves?  No, they're
gonna blame Postgres.  Or consider the case where some bozo publishes
a method for such an attack and uses it to badmouth us as insecure.

I don't think we need the headaches that will result from promising
(or at least appearing to promise) something we can't deliver.  Nor am
I convinced that we're really doing users any favors by providing such a
feature.  They'd be *far* better advised to put their critical data in a
separate database.

In short, "we can check some check-box" is a really, really bad reason
to accept a security-related feature.  If we're going to put up with
all the downsides of RLS, I want the end result to be something that's
actually secure, not something that gives the illusion of security.
And right now, I do not believe we can get past the illusion stage,
ever (certainly not in a release or two).

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-30 19:57                                           ` Josh Berkus <josh@agliodbs.com>
  2013-08-30 20:28                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-01 13:38                                             ` Re: [v9.4] row level security Heikki Linnakangas <hlinnakangas@vmware.com>
  2 siblings, 2 replies; 165+ messages in thread

From: Josh Berkus @ 2013-08-30 19:57 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 08/30/2013 12:43 PM, Tom Lane wrote:
> In short, "we can check some check-box" is a really, really bad reason
> to accept a security-related feature.  If we're going to put up with
> all the downsides of RLS, I want the end result to be something that's
> actually secure, not something that gives the illusion of security.
> And right now, I do not believe we can get past the illusion stage,
> ever (certainly not in a release or two).

Can you be more explicit about "all the downsides of RLS"?  I was just
looking over the patch, which is less than 5000 lines.  While it's not
small, we have larger patches in the CF.  So what's the specific
downsides of this?

The reason I brought up multi-tenant applications ("MTA"), BTW, is that
this is the other major potential utility of RLS, and for such users the
covert channel limitations are acceptable (as long as we publish them).
 That is, for a "thing-as-a-service" application, users are not assumed
to have unlimited access to the SQL command line anyway; RLS is employed
just to limit the damage they can do if they get access, and to limit
the disclosure if some app programmer makes a mistake.

Right now, the primary tool for doing row filtering for MTA is Veil,
which has numerous and well-known limitations.  If RLS has fewer
limitations, or is easier to deploy, maintain, and/or understand, then
it's a valuable feature for that user base, even if it doesn't address
the covert channels we've brought up at all.

That is, if RLS is your *second* level of defense, instead of your
primary defense, covert channels are not a make-or-break issue. It just
has to be better than what we had before.

Note that I have NOT done an evaluation of Veil vs. RLS for MTA at this
point.  I'm hoping someone else will ;-)

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-30 20:28                                             ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-31 05:41                                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Tom Lane @ 2013-08-30 20:28 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Josh Berkus <josh@agliodbs.com> writes:
> On 08/30/2013 12:43 PM, Tom Lane wrote:
>> In short, "we can check some check-box" is a really, really bad reason
>> to accept a security-related feature.  If we're going to put up with
>> all the downsides of RLS, I want the end result to be something that's
>> actually secure, not something that gives the illusion of security.

> Can you be more explicit about "all the downsides of RLS"?  I was just
> looking over the patch, which is less than 5000 lines.  While it's not
> small, we have larger patches in the CF.  So what's the specific
> downsides of this?

I think it's going to be an ongoing maintenance headache and an endless
source of security bugs, even disregarding covert-channel issues.  I have
pretty much zero faith in the planner changes, in particular, and would
still not have a lot if they were adequately documented, which they
absolutely are not.  The whole thing depends on nowhere-clearly-stated
assumptions that plan-time transformations will never allow an RLS check
to be bypassed.  I foresee future planner work breaking this in
non-obvious ways on a regular basis (even granting the assumption that
it's bulletproof today, which is at best unproven).

> The reason I brought up multi-tenant applications ("MTA"), BTW, is that
> this is the other major potential utility of RLS, and for such users the
> covert channel limitations are acceptable (as long as we publish them).

[ shrug... ]  You might've noticed I work for a multi-tenant shop now.
I'm still not excited about this.

> That is, if RLS is your *second* level of defense, instead of your
> primary defense, covert channels are not a make-or-break issue. It just
> has to be better than what we had before.

Yeah, that's a fair point.  I'm just not convinced that it's enough better
to justify the maintenance burden we'll be taking on.  I'm not thrilled
about the "every bug is a security bug" angle, either.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 20:28                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-31 05:41                                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-31 05:41 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Josh Berkus <josh@agliodbs.com>; Stephen Frost <sfrost@snowman.net>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/30 Tom Lane <tgl@sss.pgh.pa.us>:
> Josh Berkus <josh@agliodbs.com> writes:
>> On 08/30/2013 12:43 PM, Tom Lane wrote:
>>> In short, "we can check some check-box" is a really, really bad reason
>>> to accept a security-related feature.  If we're going to put up with
>>> all the downsides of RLS, I want the end result to be something that's
>>> actually secure, not something that gives the illusion of security.
>
>> Can you be more explicit about "all the downsides of RLS"?  I was just
>> looking over the patch, which is less than 5000 lines.  While it's not
>> small, we have larger patches in the CF.  So what's the specific
>> downsides of this?
>
> I think it's going to be an ongoing maintenance headache and an endless
> source of security bugs, even disregarding covert-channel issues.  I have
> pretty much zero faith in the planner changes, in particular, and would
> still not have a lot if they were adequately documented, which they
> absolutely are not.  The whole thing depends on nowhere-clearly-stated
> assumptions that plan-time transformations will never allow an RLS check
> to be bypassed.  I foresee future planner work breaking this in
> non-obvious ways on a regular basis (even granting the assumption that
> it's bulletproof today, which is at best unproven).
>
In general, we will adopt / enhance features as long as PostgreSQL runs
with evolution. It can never be free from bugs or maintenance, regardless
of its nature.
Later half seems to me a bit unfair because any features may conflict
with some future works, not only RLS. Also, if you have some tangible
planner enhancement plan, could you inform us which plan shall be
in progress? At least, it is impossible to adjust my implementation
because of abstract concern towards future conflict.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-09-01 13:38                                             ` Heikki Linnakangas <hlinnakangas@vmware.com>
  2013-09-01 14:06                                               ` Re: [v9.4] row level security Andres Freund <andres@2ndquadrant.com>
  2013-09-01 17:46                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Heikki Linnakangas @ 2013-09-01 13:38 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 30.08.2013 22:57, Josh Berkus wrote:
> Right now, the primary tool for doing row filtering for MTA is Veil,
> which has numerous and well-known limitations.  If RLS has fewer
> limitations, or is easier to deploy, maintain, and/or understand, then
> it's a valuable feature for that user base, even if it doesn't address
> the covert channels we've brought up at all.
>
> That is, if RLS is your*second*  level of defense, instead of your
> primary defense, covert channels are not a make-or-break issue. It just
> has to be better than what we had before.
>
> Note that I have NOT done an evaluation of Veil vs. RLS for MTA at this
> point.  I'm hoping someone else will ;-)

I'd also like to hear how Veil differs from RLS. From what I've 
understood this far, they are the same in terms of what you can and 
cannot do.

To phrase it differently: We already have RLS. It's shipped as an 
extension called Veil. Now please explain what's wrong with that 
statement, if anything.

- Heikki


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 13:38                                             ` Re: [v9.4] row level security Heikki Linnakangas <hlinnakangas@vmware.com>
@ 2013-09-01 14:06                                               ` Andres Freund <andres@2ndquadrant.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Andres Freund @ 2013-09-01 14:06 UTC (permalink / raw)
  To: Heikki Linnakangas <hlinnakangas@vmware.com>; +Cc: Josh Berkus <josh@agliodbs.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 2013-09-01 16:38:51 +0300, Heikki Linnakangas wrote:
> On 30.08.2013 22:57, Josh Berkus wrote:
> >Right now, the primary tool for doing row filtering for MTA is Veil,
> >which has numerous and well-known limitations.  If RLS has fewer
> >limitations, or is easier to deploy, maintain, and/or understand, then
> >it's a valuable feature for that user base, even if it doesn't address
> >the covert channels we've brought up at all.
> >
> >That is, if RLS is your*second*  level of defense, instead of your
> >primary defense, covert channels are not a make-or-break issue. It just
> >has to be better than what we had before.
> >
> >Note that I have NOT done an evaluation of Veil vs. RLS for MTA at this
> >point.  I'm hoping someone else will ;-)
> 
> I'd also like to hear how Veil differs from RLS. From what I've understood
> this far, they are the same in terms of what you can and cannot do.
> 
> To phrase it differently: We already have RLS. It's shipped as an extension
> called Veil. Now please explain what's wrong with that statement, if
> anything.

I don't think veil really is an argument for much in this discussion. I
don't know who in this discussion has used it, I have. Admittedly a good
bit ago, 8.2, 8.3 days I think. There hasn't been much fundamental
development since though, if my quick look is right.
Veil gives you a rather low level toolbox for developing an RLS and
you're left with a *huge* amount of work needed ontop.
It basically gives you a bunch of new datatypes (sets, bitmaps, ranges) and
provides some support for sharing variables across backends (shared
memory). That's nearly it.

Greetings,

Andres Freund

-- 
 Andres Freund	                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 13:38                                             ` Re: [v9.4] row level security Heikki Linnakangas <hlinnakangas@vmware.com>
@ 2013-09-01 17:46                                               ` Greg Smith <greg@2ndQuadrant.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Greg Smith @ 2013-09-01 17:46 UTC (permalink / raw)
  To: Heikki Linnakangas <hlinnakangas@vmware.com>; +Cc: Josh Berkus <josh@agliodbs.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; pgsql-hackers

On 9/1/13 9:38 AM, Heikki Linnakangas wrote:
> To phrase it differently: We already have RLS. It's shipped as an
> extension called Veil. Now please explain what's wrong with that
> statement, if anything.

Veil was last updated for 9.1 to work against that version, so the first 
thing is that it's two versions back from being current.

The main improvement for a few now core features, compared to their 
external/extension predecessors, is that they go through a real review 
process.  I suspect a lot of the criticisms being lobbied against the 
core RLS feature would also hit Veil if it were evaluated to the same 
standard.

Regardless, I'm seeing a few review themes pop up from this thread:

-Comparison against the Veil feature set.
-Competitive review against industry expectations, AKA "checkbox" 
compliance.
-Confirm feature set is useful to government security clearance 
applications and multi-tenant applications.  There's also a secured web 
application use case that's popped up a few times too; KaiGai has used 
secured Apache installs for example.
-Summary of known covert channels, with documentation coverage.
-Assess odds of this implementation's future issues turning into 
security bugs.  My personal hotspot here is that I'd like minimal code 
exposure to people who don't use this feature at all.  Are there parts 
here that should be compile time enabled?

Of course those are all on top of the usual code quality review.  Did I 
miss any big themes on that list?

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-08-30 20:24                                           ` Stephen Frost <sfrost@snowman.net>
  2013-09-03 00:17                                             ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
  2 siblings, 1 reply; 165+ messages in thread

From: Stephen Frost @ 2013-08-30 20:24 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

* Tom Lane (tgl@sss.pgh.pa.us) wrote:
> Stephen Frost <sfrost@snowman.net> writes:
> > We have issues with covert channels even without RLS though and holding
> > up RLS because it doesn't fix all the covert channels isn't sensible.
> 
> I think it's entirely sensible to question whether we should reject (not
> "hold up") RLS if it has major covert-channel problems.

Rejecting RLS because we've suddently realized that covert channels
exist is foolishness.  It's akin to rejecting the ability to add stored
procedures because we don't protect prosrc from people who don't own or
can't execute the function.

> The scenario I'm worried about is where somebody says "hey, Postgres has
> RLS now, I can rely on that to hide my sooper sekrit data from other users
> in the same database", and later they have a security breach through some
> covert-channel attack.  Are they going to blame themselves?  No, they're
> gonna blame Postgres.  Or consider the case where some bozo publishes
> a method for such an attack and uses it to badmouth us as insecure.

In my experience, issues are discovered, patched, and released as
security updates.  Does adding RLS mean we might have more of those?
Sure, as did column level privileges and as does *any* such increase in
the granularity of what we can provide security-wise.

> I don't think we need the headaches that will result from promising
> (or at least appearing to promise) something we can't deliver.  Nor am
> I convinced that we're really doing users any favors by providing such a
> feature.  They'd be *far* better advised to put their critical data in a
> separate database.

We've barely got cross-database queries with FDWs.  The notion that
adding such complexity into those as RLS, which each individual user
will need to figure out how to do themselves and most will likely get
far wrong and much worse than what we'd implement, is "better" for
our users is just ridiculous.

> In short, "we can check some check-box" is a really, really bad reason
> to accept a security-related feature.  If we're going to put up with
> all the downsides of RLS, I want the end result to be something that's
> actually secure, not something that gives the illusion of security.
> And right now, I do not believe we can get past the illusion stage,
> ever (certainly not in a release or two).

I'm not argueing for this because it fulfills some check-box; the
question about if it would help a given set of clients (ones which I no
longer have any direct relationship with, as it turns out) adopt PG was
asked and I answered it as best I could.

I certainly think we need to get past the 'illusion' stage also.  I'm
certainly more optimistic about that than you are but I also understand
it's not going to be perfect in the first release- but I do think it'll
be better than the 'illusion' stage.  It'll get there because we'll
continue to discuss it, people will test it, etc; as one hopes happens
with all new features, but this even more than others.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130830202406.GP2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-08-30 20:24                                           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
@ 2013-09-03 00:17                                             ` Bruce Momjian <bruce@momjian.us>
  0 siblings, 0 replies; 165+ messages in thread

From: Bruce Momjian @ 2013-09-03 00:17 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Fri, Aug 30, 2013 at 04:24:06PM -0400, Stephen Frost wrote:
> > The scenario I'm worried about is where somebody says "hey, Postgres has
> > RLS now, I can rely on that to hide my sooper sekrit data from other users
> > in the same database", and later they have a security breach through some
> > covert-channel attack.  Are they going to blame themselves?  No, they're
> > gonna blame Postgres.  Or consider the case where some bozo publishes
> > a method for such an attack and uses it to badmouth us as insecure.
> 
> In my experience, issues are discovered, patched, and released as
> security updates.  Does adding RLS mean we might have more of those?
> Sure, as did column level privileges and as does *any* such increase in
> the granularity of what we can provide security-wise.

Releasing a feature we think is perfect, and finding out later is isn't,
and fixing it, is not the same as releasing a feature we _know_ isn't
perfect, and having no idea how to fix it.

From later discussions, it seems like we have to accept that we will
never be able to avoid all convert channels, e.g. timing queries, but we
can avoid the most obvious ones, e.g. EXPLAIN, and improve it as we go.

Knowing there is no end to improving it does make some people feel
uncomfortable, and I can't think of another feature we have added with
such an open-ended nature.  We do have open-ended performance features,
but here is seems the value of the feature itself, security, is not
attainable.  Perhaps reasonable security is attainable.

I am not saying we should reject this feature --- just that the calculus
of how we decide to add this feature doesn't fit our normal analysis.

-- 
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + It's impossible for everything to be true. +


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 14:35                                           ` Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-09-04 14:35 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Fri, Aug 30, 2013 at 3:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I think it's entirely sensible to question whether we should reject (not
> "hold up") RLS if it has major covert-channel problems.

We've already had this argument before, about the security_barrier
view stuff, and that code got committed and is already released.  So
the horse is already out of the barn and no amount of wishing will put
it back in.  I haven't reviewed this patch in a long time, but I would
expect that it's basically just reusing that same infrastructure; in
fact, I'd expect that it's little more than syntactic sugar around
that infrastructure.  (If it it's instead introducing a whole new
mechanism, then I think that's reason enough to reject it right
there.)  My main question about this is whether that syntactic sugar
is really worth having given that it doesn't add any real new
functionality, not about the covert channel issues, which are already
a done deal.

And frankly, I'm with the group that says the covert channel issues
are not really a big deal.  In many real-world cases, the user can
control only the values that get subbed into queries that get sent to
the database, not the queries themselves, which eliminates a large
category of attacks.  Real-world example, from last job: sales reps
only get to see their own accounts, not accounts of other sales reps.
They could input new accounts (with sales_rep_id set to their ID) and
they could query the list of accounts (limited to those where
sales_rep_id matched their ID) - pulling either all of them or
searching by account name, both through a web application.  Yeah, a
sales rep could have launched a timing attack through the web
interface, and they could also have polled for the existence of
accounts by trying to create accounts with names that might already
exist in the system to see whether a duplicate got flagged.  But
neither attack had enough useful bandwidth to matter; a sales rep
wishing to learn our full account list (so that he could try to poach
them after leaving the company) could have learned a lot more a lot
faster via social engineering, and with less risk of being caught
doing something that would have resulted in his or her immediate
termination.

The point is, I don't think RLS needs to solve every problem.  What it
needs to do is solve one problem well, so that it can be used in
combination with other techniques to achieve a certain set of security
objectives.  If, in a particular environment, EXPLAIN is an issue,
then it can be blocked in that environment via a variety of
well-understood techniques.  That's not very hard to do even without
core support, and if we want to add core support, fine, but that's a
separate patch.  This is a patch to add row-level security, and it
deserves to be judged on how well or poorly it does that, not on
whether it solves covert channel problems that represent a mostly
orthogonal set of technical issues.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-09-04 14:45                                             ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Tom Lane @ 2013-09-04 14:45 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Robert Haas <robertmhaas@gmail.com> writes:
> On Fri, Aug 30, 2013 at 3:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I think it's entirely sensible to question whether we should reject (not
>> "hold up") RLS if it has major covert-channel problems.

> We've already had this argument before, about the security_barrier
> view stuff, and that code got committed and is already released.  So
> the horse is already out of the barn and no amount of wishing will put
> it back in.

Well, the security-barrier view stuff did not present itself as a 100%
solution.  But perhaps more to the point, it was conceptually simple to
implement, ie don't flatten views if they have this bit set, and don't
push down quals into such sub-selects unless they're marked leakproof.

> I haven't reviewed this patch in a long time, but I would
> expect that it's basically just reusing that same infrastructure; in
> fact, I'd expect that it's little more than syntactic sugar around
> that infrastructure.

I've not read it in great detail, but it isn't that.  It's whacking the
planner around in ways that I have no confidence in, and probably still
wouldn't have any confidence in if they'd been adequately documented.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 14:48                                               ` Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-09-04 14:48 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Sep 4, 2013 at 10:45 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Well, the security-barrier view stuff did not present itself as a 100%
> solution.  But perhaps more to the point, it was conceptually simple to
> implement, ie don't flatten views if they have this bit set, and don't
> push down quals into such sub-selects unless they're marked leakproof.

Right.  IMHO, this new feature should be similarly simple: when an
unprivileged user references a table, treat that as a reference to a
leakproof view over the table, with the RLS qual injected into the
view.

>> I haven't reviewed this patch in a long time, but I would
>> expect that it's basically just reusing that same infrastructure; in
>> fact, I'd expect that it's little more than syntactic sugar around
>> that infrastructure.
>
> I've not read it in great detail, but it isn't that.  It's whacking the
> planner around in ways that I have no confidence in, and probably still
> wouldn't have any confidence in if they'd been adequately documented.

If that's the case, then I agree that we should not accept it, at
least in its present form.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-09-04 14:50                                                 ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:03                                                   ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:28                                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-04 15:58                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  0 siblings, 4 replies; 165+ messages in thread

From: Tom Lane @ 2013-09-04 14:50 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Robert Haas <robertmhaas@gmail.com> writes:
> On Wed, Sep 4, 2013 at 10:45 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Well, the security-barrier view stuff did not present itself as a 100%
>> solution.  But perhaps more to the point, it was conceptually simple to
>> implement, ie don't flatten views if they have this bit set, and don't
>> push down quals into such sub-selects unless they're marked leakproof.

> Right.  IMHO, this new feature should be similarly simple: when an
> unprivileged user references a table, treat that as a reference to a
> leakproof view over the table, with the RLS qual injected into the
> view.

And for insert/update/delete, we do what exactly?

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 15:03                                                   ` Robert Haas <robertmhaas@gmail.com>
  2013-09-04 15:24                                                     ` Re: [v9.4] row level security Kevin Grittner <kgrittn@ymail.com>
  3 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-09-04 15:03 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Sep 4, 2013 at 10:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Wed, Sep 4, 2013 at 10:45 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> Well, the security-barrier view stuff did not present itself as a 100%
>>> solution.  But perhaps more to the point, it was conceptually simple to
>>> implement, ie don't flatten views if they have this bit set, and don't
>>> push down quals into such sub-selects unless they're marked leakproof.
>
>> Right.  IMHO, this new feature should be similarly simple: when an
>> unprivileged user references a table, treat that as a reference to a
>> leakproof view over the table, with the RLS qual injected into the
>> view.
>
> And for insert/update/delete, we do what exactly?

The same mechanism will prevent UPDATE and DELETE from seeing any rows
the user shouldn't be able to touch.

Simon and Greg are arguing that when an INSERT or UPDATE happens, we
ought to also check that the NEW row matches the RLS qual.  I don't
find that to be terribly important because you can accomplish the same
thing with a per-row trigger today; and I also don't think everyone
will want that behavior.  Some people will, I'm pretty sure, want to
let users "give away" rows, either unconditionally or subject to
defined restrictions.  Perhaps it's worth having, but it's a separate
feature, IMHO.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:03                                                   ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-09-04 15:24                                                     ` Kevin Grittner <kgrittn@ymail.com>
  2013-09-04 15:55                                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Kevin Grittner @ 2013-09-04 15:24 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Robert Haas <robertmhaas@gmail.com> wrote:
> Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:

>>> IMHO, this new feature should be similarly simple: when an
>>> unprivileged user references a table, treat that as a reference
>>> to a leakproof view over the table, with the RLS qual injected
>>> into the view.
>>
>> And for insert/update/delete, we do what exactly?
>
> The same mechanism will prevent UPDATE and DELETE from seeing any
> rows the user shouldn't be able to touch.

+1

> Simon and Greg are arguing that when an INSERT or UPDATE happens,
> we ought to also check that the NEW row matches the RLS qual.  I
> don't find that to be terribly important because you can
> accomplish the same thing with a per-row trigger today; and I
> also don't think everyone will want that behavior.

As an example from my Wisconsin Courts days, source documents come
in which need to be entered, which may contain a Social Security
Number, and most of the Clerk of Courts staff should be authorized
to enter that into the database.  Once it is entered, most people
should not be allowed to view it, including many of the people who
were authorized to enter it initially.  It's one thing for a line
staff person to have a handful of documents come across their desk
with SSN on a given day; it's quite another if they could dump a
list of names, addresses, dates of birth, and SSNs for the entire
database on demand.

In a way this issue is similar to the covert channel issue --
volume matters.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:03                                                   ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 15:24                                                     ` Re: [v9.4] row level security Kevin Grittner <kgrittn@ymail.com>
@ 2013-09-04 15:55                                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-04 15:55 UTC (permalink / raw)
  To: Kevin Grittner <kgrittn@ymail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/4 Kevin Grittner <kgrittn@ymail.com>:
> Robert Haas <robertmhaas@gmail.com> wrote:
>> Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> Robert Haas <robertmhaas@gmail.com> writes:
>
>>>> IMHO, this new feature should be similarly simple: when an
>>>> unprivileged user references a table, treat that as a reference
>>>> to a leakproof view over the table, with the RLS qual injected
>>>> into the view.
>>>
>>> And for insert/update/delete, we do what exactly?
>>
>> The same mechanism will prevent UPDATE and DELETE from seeing any
>> rows the user shouldn't be able to touch.
>
> +1
>
>> Simon and Greg are arguing that when an INSERT or UPDATE happens,
>> we ought to also check that the NEW row matches the RLS qual.  I
>> don't find that to be terribly important because you can
>> accomplish the same thing with a per-row trigger today; and I
>> also don't think everyone will want that behavior.
>
> As an example from my Wisconsin Courts days, source documents come
> in which need to be entered, which may contain a Social Security
> Number, and most of the Clerk of Courts staff should be authorized
> to enter that into the database.  Once it is entered, most people
> should not be allowed to view it, including many of the people who
> were authorized to enter it initially.  It's one thing for a line
> staff person to have a handful of documents come across their desk
> with SSN on a given day; it's quite another if they could dump a
> list of names, addresses, dates of birth, and SSNs for the entire
> database on demand.
>
> In a way this issue is similar to the covert channel issue --
> volume matters.
>
I think an important nature of this behavior is it is configurable.

In case when both of reader and writer side need to have same
security policy, it's good. One configuration allows to apply
a consistent security policy to fetch a row from table, and to
write a row to table.

If they don't want to check security policy on writer side,
all they need to do is setting a security policy for SELECT
only, even though its functionality is not implemented yet.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 15:22                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:46                                                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-04 16:50                                                     ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  3 siblings, 3 replies; 165+ messages in thread

From: Tom Lane @ 2013-09-04 15:22 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Robert Haas <robertmhaas@gmail.com> writes:
> On Wed, Sep 4, 2013 at 10:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> Robert Haas <robertmhaas@gmail.com> writes:
>>> Right.  IMHO, this new feature should be similarly simple: when an
>>> unprivileged user references a table, treat that as a reference to a
>>> leakproof view over the table, with the RLS qual injected into the
>>> view.

>> And for insert/update/delete, we do what exactly?

> The same mechanism will prevent UPDATE and DELETE from seeing any rows
> the user shouldn't be able to touch.

No, it won't, because we don't support direct update/delete on views
(and if you look, you'll notice the auto-updatable-view stuff doesn't
think a security-barrier view is auto-updatable).

AFAICT, to deal with update/delete the RLS patch needs to constrain order
of qual application without the crutch of having a separate level of
subquery; and it's that behavior that I have zero confidence in, either
as to whether it works as submitted or as to our odds of not breaking it
in the future.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 15:46                                                     ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-04 15:46 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/4 Tom Lane <tgl@sss.pgh.pa.us>:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Wed, Sep 4, 2013 at 10:50 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> Robert Haas <robertmhaas@gmail.com> writes:
>>>> Right.  IMHO, this new feature should be similarly simple: when an
>>>> unprivileged user references a table, treat that as a reference to a
>>>> leakproof view over the table, with the RLS qual injected into the
>>>> view.
>
>>> And for insert/update/delete, we do what exactly?
>
>> The same mechanism will prevent UPDATE and DELETE from seeing any rows
>> the user shouldn't be able to touch.
>
> No, it won't, because we don't support direct update/delete on views
> (and if you look, you'll notice the auto-updatable-view stuff doesn't
> think a security-barrier view is auto-updatable).
>
> AFAICT, to deal with update/delete the RLS patch needs to constrain order
> of qual application without the crutch of having a separate level of
> subquery; and it's that behavior that I have zero confidence in, either
> as to whether it works as submitted or as to our odds of not breaking it
> in the future.
>
Are you suggesting to rewrite update / delete statement to filter out
unprivileged rows from manipulation?
Yes. I also thought it is a simple solution that does not need additional
enhancement to allow update / delete to take sub-query on top of reader
side plan.

For example, if security policy is (t1.owner = current_user) and the given
query was "UPDATE t1 SET value = value || '_updated' WHERE value like '%abc%'",
this query may be able to rewritten as follows:
  UPDATE t1 SET value = value || '_updated' WHERE tid = (
    SELECT tid FROM t1 WHERE t1.owner = current_user
  ) AND value like '%abc%';

This approach makes implementation simple, but it has to scan the
relation twice, thus its performance it not ideal, according to the
past discussion.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 16:50                                                     ` Robert Haas <robertmhaas@gmail.com>
  2 siblings, 0 replies; 165+ messages in thread

From: Robert Haas @ 2013-09-04 16:50 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Sep 4, 2013 at 11:22 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> The same mechanism will prevent UPDATE and DELETE from seeing any rows
>> the user shouldn't be able to touch.
>
> No, it won't, because we don't support direct update/delete on views
> (and if you look, you'll notice the auto-updatable-view stuff doesn't
> think a security-barrier view is auto-updatable).
>
> AFAICT, to deal with update/delete the RLS patch needs to constrain order
> of qual application without the crutch of having a separate level of
> subquery; and it's that behavior that I have zero confidence in, either
> as to whether it works as submitted or as to our odds of not breaking it
> in the future.

I don't really see why.  AIUI, the ModifyTable node just needs to get
the right TIDs.  It's not like that has to be stacked directly on top
of a scan; indeed, in cases like UPDATE .. FROM and DELETE .. USING it
already isn't.  Maybe there's some reason why the intervening level
can be a Join but not a  SubqueryScan, but if so I'd expect we could
find some way of lifting that limitation without suffering too much
pain.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-11-04 08:08                                                     ` Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2 siblings, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2013-11-04 08:08 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; Robert Haas <robertmhaas@gmail.com>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 09/04/2013 11:22 PM, Tom Lane wrote:
> AFAICT, to deal with update/delete the RLS patch needs to constrain order
> of qual application without the crutch of having a separate level of
> subquery; and it's that behavior that I have zero confidence in, either
> as to whether it works as submitted or as to our odds of not breaking it
> in the future.

Wouldn't CASE do that job, albeit in a somewhat ugly manner, and also
protect against malicious RLS functions?

For any subclause in a WHERE clause "(a) OR (b)" you can instead write a
short-circuit OR version as:

  CASE WHEN (a) THEN true ELSE (b) END

no?

So, given a row security condition like "(rowowner = current_user AND
rls_malicious_leak())" on table "test", this:

SELECT * FROM test WHERE client_leak_fn();

could be rewritten by row security as:

SELECT *
FROM test
WHERE

CASE WHEN
  CASE WHEN is_superuser() THEN true
  ELSE (rowowner = current_user AND rls_malicious_leak())
  END
THEN
  client_leak_fn()
END;


in other words: if the user is the superuser, don't evaluate the RLS
predicate, just assume they have the right to see the row. Otherwise
evaluate the RLS predicate to determine whether they can see the row. In
either case, if they have the right to see the row, run the original
WHERE clause.

My main concern is that it'd be relying on a guarantee that CASE is
always completely ordered, and that might not be ideal. It's also
hideously ugly, and future optimiser smarts could create unexpected issues.

Unless you propose the creaton of a new short-circuit left-to-right
order guaranteed OR operator, and think the planner/optimizer should be
taught special case rules to prevent it from doing pull-up / push-down
or pre-evaluating subclauses for it, I suspect the notion of using
security barrier views or subqueries is still going to be the sanest way
to do it.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-04 14:37                                                       ` Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Tom Lane @ 2013-11-04 14:37 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Craig Ringer <craig@2ndquadrant.com> writes:
> On 09/04/2013 11:22 PM, Tom Lane wrote:
>> AFAICT, to deal with update/delete the RLS patch needs to constrain order
>> of qual application without the crutch of having a separate level of
>> subquery; and it's that behavior that I have zero confidence in, either
>> as to whether it works as submitted or as to our odds of not breaking it
>> in the future.

> Wouldn't CASE do that job, albeit in a somewhat ugly manner, and also
> protect against malicious RLS functions?

You mean wrap all the query quals in a CASE?  Sure, if you didn't mind
totally destroying any optimization possibilities.  If you did that,
every table scan would become a seqscan and every join a nestloop.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-11-04 15:17                                                         ` Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-11-04 15:17 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Craig Ringer <craig@2ndquadrant.com>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Mon, Nov 4, 2013 at 9:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Craig Ringer <craig@2ndquadrant.com> writes:
>> On 09/04/2013 11:22 PM, Tom Lane wrote:
>>> AFAICT, to deal with update/delete the RLS patch needs to constrain order
>>> of qual application without the crutch of having a separate level of
>>> subquery; and it's that behavior that I have zero confidence in, either
>>> as to whether it works as submitted or as to our odds of not breaking it
>>> in the future.
>
>> Wouldn't CASE do that job, albeit in a somewhat ugly manner, and also
>> protect against malicious RLS functions?
>
> You mean wrap all the query quals in a CASE?  Sure, if you didn't mind
> totally destroying any optimization possibilities.  If you did that,
> every table scan would become a seqscan and every join a nestloop.

I'd still like to here what's wrong with what I said here:

http://www.postgresql.org/message-id/CA+TgmoYr1PHw3X9vnVuWDcfXkzK2p_jhtWc0fV2Q58NEgcxyTA@mail.gmail....

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-11-05 01:46                                                           ` Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2013-11-05 01:46 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 11/04/2013 11:17 PM, Robert Haas wrote:
> I'd still like to here what's wrong with what I said here:
> 
> http://www.postgresql.org/message-id/CA+TgmoYr1PHw3X9vnVuWDcfXkzK2p_jhtWc0fV2Q58NEgcxyTA@mail.gmail....

For me, just my understanding. I'm still too new to the planner and
rewriter to grasp that properly as written.

I was responding to Tom's objection, and he makes a good point about
CASE and optimisation. We have to be free to re-order and pre-evaluate
where LEAKPROOF flags make it safe and permissible, without ever
otherwise doing so. That makes the SECURITY BARRIER subquery look
better, since the limited pull-up / push-down is already implemented there.

Robert, any suggesitons on how to approach what you suggest? I'm pretty
new to the planner's guts, but I know there've been some complaints
about the way the current RLS code fiddles with Vars when it changes a
direct scan of a rel into a subquery scan.

The code in:

https://github.com/ringerc/postgres/blob/rls-9.4/src/backend/optimizer/prep/prepunion.c#L1647

and

https://github.com/ringerc/postgres/blob/rls-9.4/src/backend/optimizer/prep/prepunion.c#L1591

seems to be the one folks were complaining about earlier.


-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-05 13:36                                                             ` Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-11-05 13:36 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Mon, Nov 4, 2013 at 8:46 PM, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 11/04/2013 11:17 PM, Robert Haas wrote:
>> I'd still like to here what's wrong with what I said here:
>>
>> http://www.postgresql.org/message-id/CA+TgmoYr1PHw3X9vnVuWDcfXkzK2p_jhtWc0fV2Q58NEgcxyTA@mail.gmail....
>
> For me, just my understanding. I'm still too new to the planner and
> rewriter to grasp that properly as written.
>
> I was responding to Tom's objection, and he makes a good point about
> CASE and optimisation. We have to be free to re-order and pre-evaluate
> where LEAKPROOF flags make it safe and permissible, without ever
> otherwise doing so. That makes the SECURITY BARRIER subquery look
> better, since the limited pull-up / push-down is already implemented there.
>
> Robert, any suggesitons on how to approach what you suggest? I'm pretty
> new to the planner's guts, but I know there've been some complaints
> about the way the current RLS code fiddles with Vars when it changes a
> direct scan of a rel into a subquery scan.
>
> The code in:
>
> https://github.com/ringerc/postgres/blob/rls-9.4/src/backend/optimizer/prep/prepunion.c#L1647
>
> and
>
> https://github.com/ringerc/postgres/blob/rls-9.4/src/backend/optimizer/prep/prepunion.c#L1591
>
> seems to be the one folks were complaining about earlier.

I haven't studied this patch in detail, but I see why there's some
unhappiness about that code: it's an RLS-specific kluge.  Just
shooting from the hip here, maybe we should attack the problem of
making security-barrier views updatable first, as a separate patch.  I
would think that if we make that work, this will also work without,
hopefully, any special hackery.  And we'd get a separate,
independently useful feature out of it, too.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-11-06 06:38                                                               ` Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 09:02                                                                 ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-11-06 19:12                                                                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-11 21:39                                                                 ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  0 siblings, 4 replies; 165+ messages in thread

From: Craig Ringer @ 2013-11-06 06:38 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 11/05/2013 09:36 PM, Robert Haas wrote:
> I haven't studied this patch in detail, but I see why there's some
> unhappiness about that code: it's an RLS-specific kluge.  Just
> shooting from the hip here, maybe we should attack the problem of
> making security-barrier views updatable first, as a separate patch.

That's the approach I've been considering. There are a few wrinkles with
it, though:

(a) Updatable views are implemented in the rewriter, not the planner.
The rewriter is not re-run when plans are invalidated or when the
session authorization changes, etc. This means that we can't simply omit
the RLS predicate for superuser because the same rewritten parse tree
might get used for both superuser and non-superuser queries.

Options:

* Store the before-rewrite parse tree when RLS is discovered on one of
the rels in the tree. Re-run the rewriter when re-planning. Ensure a
change in current user always invalidates plans.

* Declare that it's not legal to run a query originally parsed and
rewritten as superuser as a non-superuser or vice versa. This would
cause a great deal of pain with PL/PgSQL.

* Always add the RLS predicate and solve the problem of reliably
short-circuiting the user-supplied predicate for RLS-exempt users.  We'd
need a way to allow direct (not query-based) COPY TO for tables with RLS
applied, preventing the rewriting of direct table access into subqueries
for COPY, but since we never save plans for COPY that may be fine.

* ... ?


(b) Inheritance is a problem when RLS is done in the rewriter. As I
understood it from Kohei KaiGai's description to me earlier, there was a
strong preference on -hackers to enforce RLS predicates for child and
parent tables completely independently. That's how RLS currently works,
but it might be hard to get the same effect when applying RLS in the
rewriter. We'd need to solve that, or redefine RLS's behaviour so that
the predicate on a parent table applies to any child tables too.
Personally I'd prefer the latter.

(c) RLS might interact differently with rules declared on tables if
implemented in the rewriter, so some investigation into that would be
needed.

> I
> would think that if we make that work, this will also work without,
> hopefully, any special hackery.  And we'd get a separate,
> independently useful feature out of it, too.

I tend to agree. I'm just a bit concerned about dealing with the issues
around RLS-exempt operations and users.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-06 09:02                                                                 ` Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-11-06 09:23                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 18:14                                                                   ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  3 siblings, 2 replies; 165+ messages in thread

From: Dean Rasheed @ 2013-11-06 09:02 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 6 November 2013 06:38, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 11/05/2013 09:36 PM, Robert Haas wrote:
>> I haven't studied this patch in detail, but I see why there's some
>> unhappiness about that code: it's an RLS-specific kluge.  Just
>> shooting from the hip here, maybe we should attack the problem of
>> making security-barrier views updatable first, as a separate patch.
>
> That's the approach I've been considering.

FWIW, I have a half-complete patch to make SB views auto-updatable,
borrowing some code from the RLS patch. I hadn't looked at it for some
time, but I've just brushed the dust off it and rebased it to head.
There are several things I don't like in it, and it's probably not
close to being committable, but it might give you some ideas if you
were going to take a crack at this yourself.

The basic idea is to have rewriteTargetView() collect up any quals
from SB views in a new list on the target RTE, instead of adding them
to the main query's predicates (it needs to be a list of SB quals, in
case there are SB views on top of other SB views, in which case they
need to be kept separate from one another). Then at the end of the
rewriting process (after any views referenced in the SB quals have
been expanded), a new piece of code kicks in to process any RTEs with
SB quals, turning them into (possibly nested) subquery RTEs.

The complication is that the query's resultRelation RTE mustn't be a
subquery. This is handled this in a similar way to the
trigger-updatable views code, producing 2 RTEs --- the resultRelation
RTE becomes a direct reference to the base relation, and a separate
subquery RTE acts as the source of rows to update.

An additional complication arising from that approach is that the
planner's preptlist code (expand_targetlist) may add additional
targetlist entries representing identity assignments, so it needs to
know that the source RTE is now different from the result RTE in the
query, otherwise it
all falls apart because the target RTE isn't in the query's fromList.
That's one of the things I don't like about this. In fact it seems
messy that there are 2 separate places in the code that expand the
targetlist.

Anyway, feel free to do what you like with this. I wasn't planning on
submitting it to the next commitfest myself, because my non-PG
workload is too high, and I don't expect to get much time to hack on
postgresql during the next couple of months.

Regards,
Dean

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] updatable-sb-views.patch (51.0K, ../../CAEZATCXP-ZLtR0S3gMZwGdeYR=VEqoUoVCt9ntvkzdU9zU7RQQ@mail.gmail.com/2-updatable-sb-views.patch)
  download | inline diff:
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index 246a3a9..bfc4fef
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** postgresAddForeignUpdateTargets(Query *p
*** 1093,1099 ****
  	 */
  
  	/* Make a Var representing the desired value */
! 	var = makeVar(parsetree->resultRelation,
  				  SelfItemPointerAttributeNumber,
  				  TIDOID,
  				  -1,
--- 1093,1099 ----
  	 */
  
  	/* Make a Var representing the desired value */
! 	var = makeVar(parsetree->sourceRelation,
  				  SelfItemPointerAttributeNumber,
  				  TIDOID,
  				  -1,
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
new file mode 100644
index e0fbe1e..888410f
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
*************** CREATE VIEW vista AS SELECT text 'Hello
*** 323,334 ****
         or set-returning functions.
        </para>
       </listitem>
- 
-      <listitem>
-       <para>
-        The view must not have the <literal>security_barrier</> property.
-       </para>
-      </listitem>
      </itemizedlist>
     </para>
  
--- 323,328 ----
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 65f3b98..ffe0e20
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 2003,2008 ****
--- 2003,2009 ----
  	COPY_SCALAR_FIELD(checkAsUser);
  	COPY_BITMAPSET_FIELD(selectedCols);
  	COPY_BITMAPSET_FIELD(modifiedCols);
+ 	COPY_NODE_FIELD(securityQuals);
  
  	return newnode;
  }
*************** _copyQuery(const Query *from)
*** 2451,2456 ****
--- 2452,2458 ----
  	COPY_SCALAR_FIELD(queryId);
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
+ 	COPY_SCALAR_FIELD(sourceRelation);
  	COPY_SCALAR_FIELD(resultRelation);
  	COPY_SCALAR_FIELD(hasAggs);
  	COPY_SCALAR_FIELD(hasWindowFuncs);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 4c9b05e..27331d5
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalQuery(const Query *a, const Query
*** 846,851 ****
--- 846,852 ----
  	/* we intentionally ignore queryId, since it might not be set */
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
+ 	COMPARE_SCALAR_FIELD(sourceRelation);
  	COMPARE_SCALAR_FIELD(resultRelation);
  	COMPARE_SCALAR_FIELD(hasAggs);
  	COMPARE_SCALAR_FIELD(hasWindowFuncs);
*************** _equalRangeTblEntry(const RangeTblEntry
*** 2258,2263 ****
--- 2259,2265 ----
  	COMPARE_SCALAR_FIELD(checkAsUser);
  	COMPARE_BITMAPSET_FIELD(selectedCols);
  	COMPARE_BITMAPSET_FIELD(modifiedCols);
+ 	COMPARE_NODE_FIELD(securityQuals);
  
  	return true;
  }
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index 908f397..38cced1
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** range_table_walker(List *rtable,
*** 2008,2013 ****
--- 2008,2016 ----
  					return true;
  				break;
  		}
+ 
+ 		if (walker(rte->securityQuals, context))
+ 			return true;
  	}
  	return false;
  }
*************** range_table_mutator(List *rtable,
*** 2731,2736 ****
--- 2734,2740 ----
  				MUTATE(newrte->values_lists, rte->values_lists, List *);
  				break;
  		}
+ 		MUTATE(newrte->securityQuals, rte->securityQuals, List *);
  		newrt = lappend(newrt, newrte);
  	}
  	return newrt;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 817b149..092e7b9
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outQuery(StringInfo str, const Query *n
*** 2239,2244 ****
--- 2239,2245 ----
  	else
  		appendStringInfoString(str, " :utilityStmt <>");
  
+ 	WRITE_INT_FIELD(sourceRelation);
  	WRITE_INT_FIELD(resultRelation);
  	WRITE_BOOL_FIELD(hasAggs);
  	WRITE_BOOL_FIELD(hasWindowFuncs);
*************** _outRangeTblEntry(StringInfo str, const
*** 2411,2416 ****
--- 2412,2418 ----
  	WRITE_OID_FIELD(checkAsUser);
  	WRITE_BITMAPSET_FIELD(selectedCols);
  	WRITE_BITMAPSET_FIELD(modifiedCols);
+ 	WRITE_NODE_FIELD(securityQuals);
  }
  
  static void
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index d325bb3..719d6ba
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 198,203 ****
--- 198,204 ----
  	local_node->queryId = 0;	/* not saved in output format */
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
+ 	READ_INT_FIELD(sourceRelation);
  	READ_INT_FIELD(resultRelation);
  	READ_BOOL_FIELD(hasAggs);
  	READ_BOOL_FIELD(hasWindowFuncs);
*************** _readRangeTblEntry(void)
*** 1251,1256 ****
--- 1252,1258 ----
  	READ_OID_FIELD(checkAsUser);
  	READ_BITMAPSET_FIELD(selectedCols);
  	READ_BITMAPSET_FIELD(modifiedCols);
+ 	READ_NODE_FIELD(securityQuals);
  
  	READ_DONE();
  }
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index d8aa35d..19031bf
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** inheritance_planner(PlannerInfo *root)
*** 891,896 ****
--- 891,898 ----
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
  				}
  				rti++;
  			}
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index fb67f9e..75a6c5d
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 36,45 ****
  #include "utils/rel.h"
  
  
! static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
   * preprocess_targetlist
   *	  Driver for preprocessing the parse tree targetlist.
--- 36,74 ----
  #include "utils/rel.h"
  
  
! static List *expand_targetlist(Query *parse, List *tlist, int command_type,
! 				  Index source_relation, Index result_relation,
! 				  List *range_table);
  
  
+ static AttrNumber
+ lookup_varattno(Query *parse, AttrNumber attno, Index rt_index, List *rtables)
+ {
+ 	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+ 
+ 	if (rte->rtekind == RTE_SUBQUERY && rt_index == parse->sourceRelation)
+ 	{
+ 		ListCell   *cell;
+ 
+ 		foreach (cell, rte->subquery->targetList)
+ 		{
+ 			TargetEntry *tle = lfirst(cell);
+ 			Var			*var;
+ 
+ 			if (IsA(tle->expr, Const))
+ 				continue;
+ 
+ 			var = (Var *) tle->expr;
+ 			Assert(IsA(var, Var));
+ 
+ 			if (var->varattno == attno)
+ 				return tle->resno;
+ 		}
+ 		elog(ERROR, "attno %d not found in subquery targetlist", attno);
+ 	}
+ 	return attno;
+ }
+ 
  /*
   * preprocess_targetlist
   *	  Driver for preprocessing the parse tree targetlist.
*************** preprocess_targetlist(PlannerInfo *root,
*** 73,80 ****
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
! 		tlist = expand_targetlist(tlist, command_type,
  								  result_relation, range_table);
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
--- 102,113 ----
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
! 	{
! 		Index	source_relation = parse->sourceRelation > 0 ?
! 								  parse->sourceRelation : result_relation;
! 		tlist = expand_targetlist(parse, tlist, command_type, source_relation,
  								  result_relation, range_table);
+ 	}
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
*************** preprocess_targetlist(PlannerInfo *root,
*** 96,102 ****
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  SelfItemPointerAttributeNumber,
  						  TIDOID,
  						  -1,
  						  InvalidOid,
--- 129,136 ----
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  lookup_varattno(parse, SelfItemPointerAttributeNumber,
! 										  rc->rti, range_table),
  						  TIDOID,
  						  -1,
  						  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 112,118 ****
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  TableOidAttributeNumber,
  							  OIDOID,
  							  -1,
  							  InvalidOid,
--- 146,153 ----
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  lookup_varattno(parse, TableOidAttributeNumber,
! 											  rc->rti, range_table),
  							  OIDOID,
  							  -1,
  							  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 194,201 ****
   *	  non-junk attributes appear in proper field order.
   */
  static List *
! expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
--- 229,237 ----
   *	  non-junk attributes appear in proper field order.
   */
  static List *
! expand_targetlist(Query *parse, List *tlist, int command_type,
! 				  Index source_relation, Index result_relation,
! 				  List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
*************** expand_targetlist(List *tlist, int comma
*** 298,305 ****
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(result_relation,
! 													attrno,
  													atttype,
  													atttypmod,
  													attcollation,
--- 334,344 ----
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(source_relation,
! 													lookup_varattno(parse,
! 														attrno,
! 														source_relation,
! 														range_table),
  													atttype,
  													atttypmod,
  													attcollation,
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index c52a374..660d62f
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 14,19 ****
--- 14,20 ----
  #include "postgres.h"
  
  #include "access/sysattr.h"
+ #include "catalog/heap.h"
  #include "catalog/pg_type.h"
  #include "commands/trigger.h"
  #include "foreign/fdwapi.h"
*************** static Query *fireRIRrules(Query *parset
*** 63,68 ****
--- 64,70 ----
  			 bool forUpdatePushedDown);
  static bool view_has_instead_trigger(Relation view, CmdType event);
  static Bitmapset *adjust_view_column_set(Bitmapset *cols, List *targetlist);
+ static Query *rewriteSecurityQuals(Query *parsetree);
  
  
  /*
*************** view_query_is_auto_updatable(Query *view
*** 2048,2061 ****
  		return gettext_noop("Views that return set-returning functions are not automatically updatable.");
  
  	/*
- 	 * For now, we also don't support security-barrier views, because of the
- 	 * difficulty of keeping upper-level qual expressions away from
- 	 * lower-level data.  This might get relaxed in the future.
- 	 */
- 	if (security_barrier)
- 		return gettext_noop("Security-barrier views are not automatically updatable.");
- 
- 	/*
  	 * The view query should select from a single base relation, which must be
  	 * a table or another view.
  	 */
--- 2050,2055 ----
*************** rewriteTargetView(Query *parsetree, Rela
*** 2664,2669 ****
--- 2658,2671 ----
  												   view_targetlist);
  
  	/*
+ 	 * Move any security barrier quals from the view RTE onto the new target
+ 	 * RTE.  Any such quals should now apply to the new target RTE and will not
+ 	 * reference the original view RTE in the rewritten query.
+ 	 */
+ 	new_rte->securityQuals = view_rte->securityQuals;
+ 	view_rte->securityQuals = NIL;
+ 
+ 	/*
  	 * For UPDATE/DELETE, rewriteTargetListUD will have added a wholerow junk
  	 * TLE for the view to the end of the targetlist, which we no longer need.
  	 * Remove it to avoid unnecessary work when we process the targetlist.
*************** rewriteTargetView(Query *parsetree, Rela
*** 2743,2748 ****
--- 2745,2754 ----
  	 * only adjust their varnos to reference the new target (just the same as
  	 * we did with the view targetlist).
  	 *
+ 	 * Note that there is special-case handling for the quals of a security
+ 	 * barrier view, since they need to be kept separate from any user-supplied
+ 	 * quals - see rewriteSecurityQuals.
+ 	 *
  	 * For INSERT, the view's quals can be ignored in the main query.
  	 */
  	if (parsetree->commandType != CMD_INSERT &&
*************** rewriteTargetView(Query *parsetree, Rela
*** 2751,2757 ****
  		Node	   *viewqual = (Node *) copyObject(viewquery->jointree->quals);
  
  		ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0);
! 		AddQual(parsetree, (Node *) viewqual);
  	}
  
  	/*
--- 2757,2774 ----
  		Node	   *viewqual = (Node *) copyObject(viewquery->jointree->quals);
  
  		ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0);
! 
! 		if (RelationIsSecurityView(view))
! 		{
! 			/*
! 			 * Note: the parsetree has been mutated, so the new_rte pointer is
! 			 * stale and needs to be re-computed.
! 			 */
! 			new_rte = rt_fetch(new_rt_index, parsetree->rtable);
! 			new_rte->securityQuals = lcons(viewqual, new_rte->securityQuals);
! 		}
! 		else
! 			AddQual(parsetree, (Node *) viewqual);
  	}
  
  	/*
*************** rewriteTargetView(Query *parsetree, Rela
*** 2812,2824 ****
  				/*
  				 * Make sure that the query is marked correctly if the added
  				 * qual has sublinks.  We can skip this check if the query is
! 				 * already marked, or if the command is an UPDATE, in which
! 				 * case the same qual will have already been added to the
! 				 * query's WHERE clause, and AddQual will have already done
! 				 * this check.
  				 */
  				if (!parsetree->hasSubLinks &&
! 					parsetree->commandType != CMD_UPDATE)
  					parsetree->hasSubLinks = checkExprHasSubLink(wco->qual);
  			}
  		}
--- 2829,2842 ----
  				/*
  				 * Make sure that the query is marked correctly if the added
  				 * qual has sublinks.  We can skip this check if the query is
! 				 * already marked, or if the command is an UPDATE to a
! 				 * non-security-barrier view, in which case the same qual will
! 				 * have already been added to the query's WHERE clause, and
! 				 * AddQual will have already done this check.
  				 */
  				if (!parsetree->hasSubLinks &&
! 					(parsetree->commandType != CMD_UPDATE ||
! 					 RelationIsSecurityView(view)))
  					parsetree->hasSubLinks = checkExprHasSubLink(wco->qual);
  			}
  		}
*************** rewriteTargetView(Query *parsetree, Rela
*** 2829,2834 ****
--- 2847,3291 ----
  
  
  /*
+  * rewriteSecurityQual -
+  *	  rewrite the specified security barrier qual on a query RTE, turning the
+  *	  RTE into a subquery.
+  */
+ static Query *
+ rewriteSecurityQual(Query *parsetree, int rt_index,
+ 					bool is_result_relation, Node *qual)
+ {
+ 	RangeTblEntry  *rte;
+ 	List		   *targetlist = NIL;
+ 	List		   *rte_targetlist = NIL;
+ 	List		   *colnames = NIL;
+ 	Relation		relation;
+ 	AttrNumber		attno;
+ 	Var			   *var;
+ 	TargetEntry	   *tle;
+ 	Query		   *subquery;
+ 	RangeTblEntry  *subrte;
+ 	RangeTblRef	   *subrtr;
+ 	ListCell	   *cell;
+ 
+ 	rte = rt_fetch(rt_index, parsetree->rtable);
+ 
+ 	/*
+ 	 * There are 2 possible cases:
+ 	 *
+ 	 * 1. A relation RTE, which we turn into a subquery RTE containing all
+ 	 * referenced columns.
+ 	 *
+ 	 * 2. A subquery RTE (either from a prior call to this function or from an
+ 	 * expanded view).  In this case we build a new subquery on top of it to
+ 	 * isolate this security barrier qual from any other quals.
+ 	 */
+ 	switch (rte->rtekind)
+ 	{
+ 		case RTE_RELATION:
+ 			/*
+ 			 * Build the subquery targetlist from the non-dropped columns of
+ 			 * the underlying table and an RTE targetlist to map varattnos in
+ 			 * the main query onto the new subquery columns.
+ 			 */
+ 			relation = heap_open(rte->relid, NoLock);
+ 
+ 			for (attno = 1; attno <= relation->rd_att->natts; attno++)
+ 			{
+ 				Form_pg_attribute att_tup;
+ 				char	   *attname;
+ 
+ 				/* Ignore dropped attributes */
+ 				att_tup = relation->rd_att->attrs[attno - 1];
+ 				if (att_tup->attisdropped)
+ 					continue;
+ 
+ 				/* target entry for new subquery targetlist */
+ 				var =  makeVar(1,
+ 							   attno,
+ 							   att_tup->atttypid,
+ 							   att_tup->atttypmod,
+ 							   att_tup->attcollation,
+ 							   0);
+ 				attname = NameStr(att_tup->attname);
+ 
+ 				tle = makeTargetEntry((Expr *) var,
+ 									  list_length(targetlist) + 1,
+ 									  pstrdup(attname),
+ 									  false);
+ 				targetlist = lappend(targetlist, tle);
+ 
+ 				/* top-level target entry for rewriting the main query */
+ 				var = copyObject(var);
+ 				var->varno = rt_index;
+ 				var->varattno = list_length(targetlist);
+ 				tle = makeTargetEntry((Expr *) var,
+ 									  attno,
+ 									  pstrdup(attname),
+ 									  false);
+ 				rte_targetlist = lappend(rte_targetlist, tle);
+ 
+ 				colnames = lappend(colnames, makeString(pstrdup(attname)));
+ 			}
+ 
+ 			/*
+ 			 * Then include any junk system attributes.  These must come at
+ 			 * the end of the targetlist.
+ 			 */
+ 			for (attno = FirstLowInvalidHeapAttributeNumber + 1;
+ 				 attno <= InvalidAttrNumber; attno++)
+ 			{
+ 				Form_pg_attribute att_tup;
+ 				char	   *attname;
+ 
+ 				if (attno == ObjectIdAttributeNumber &&
+ 					!relation->rd_rel->relhasoids)
+ 					continue;
+ 
+ 				if (attno == InvalidAttrNumber)
+ 				{
+ 					/* whole-row attribute */
+ 					var = makeWholeRowVar(rte, 1, 0, false);
+ 					attname = "wholerow";
+ 				}
+ 				else
+ 				{
+ 					/* system attribute */
+ 					att_tup = SystemAttributeDefinition(attno,
+ 												relation->rd_rel->relhasoids);
+ 
+ 					var =  makeVar(1,
+ 								   attno,
+ 								   att_tup->atttypid,
+ 								   att_tup->atttypmod,
+ 								   att_tup->attcollation,
+ 								   0);
+ 					attname = NameStr(att_tup->attname);
+ 				}
+ 
+ 				/* target entry for new subquery targetlist */
+ 				tle = makeTargetEntry((Expr *) var,
+ 									  list_length(targetlist) + 1,
+ 									  pstrdup(attname),
+ 									  false);
+ 				targetlist = lappend(targetlist, tle);
+ 
+ 				/* top-level target entry for rewriting the main query */
+ 				var = copyObject(var);
+ 				var->varno = rt_index;
+ 				var->varattno = list_length(targetlist);
+ 				tle = makeTargetEntry((Expr *) var,
+ 									  attno,
+ 									  pstrdup(attname),
+ 									  false);
+ 				rte_targetlist = lappend(rte_targetlist, tle);
+ 
+ 				colnames = lappend(colnames, makeString(pstrdup(attname)));
+ 			}
+ 			heap_close(relation, NoLock);
+ 
+ 			/*
+ 			 * Turn the main relation RTE into a security barrier subquery
+ 			 * RTE, moving all permissions checks and rowMarks down into the
+ 			 * subquery.
+ 			 */
+ 			subquery = makeNode(Query);
+ 			subquery->commandType = CMD_SELECT;
+ 			subquery->querySource = QSRC_INSTEAD_RULE;
+ 			subquery->targetList = targetlist;
+ 
+ 			subrte = copyObject(rte);
+ 			subrte->inFromCl = true;
+ 			subrte->securityQuals = NIL;
+ 			subquery->rtable = list_make1(subrte);
+ 
+ 			subrtr = makeNode(RangeTblRef);
+ 			subrtr->rtindex = 1;
+ 			subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
+ 			subquery->hasSubLinks = checkExprHasSubLink(qual);
+ 
+ 			foreach(cell, parsetree->rowMarks)
+ 			{
+ 				RowMarkClause *rc = (RowMarkClause *) lfirst(cell);
+ 				if (rc->rti == rt_index)
+ 				{
+ 					parsetree->rowMarks = list_delete(parsetree->rowMarks,
+ 													  rc);
+ 					rc->rti = 1;
+ 					rc->pushedDown = true;
+ 					subquery->rowMarks = list_make1(rc);
+ 					break;
+ 				}
+ 			}
+ 
+ 			/*
+ 			 * If this RTE was the result relation, then we need to lock the
+ 			 * rows coming from it.  Note that by the time we get here, this
+ 			 * RTE will no longer be the result relation, so we have to rely
+ 			 * on the flag passed in.
+ 			 */
+ 			if (is_result_relation)
+ 				applyLockingClause(subquery, 1, LCS_FORUPDATE, false, true);
+ 
+ 			rte->rtekind = RTE_SUBQUERY;
+ 			rte->relid = InvalidOid;
+ 			rte->subquery = subquery;
+ 			rte->security_barrier = true;
+ 			rte->eref = makeAlias(rte->eref->aliasname, colnames);
+ 			rte->inh = false;			/* must not be set for a subquery */
+ 
+ 			/* the permissions checks have now been moved down */
+ 			rte->requiredPerms = 0;
+ 			rte->checkAsUser = InvalidOid;
+ 			rte->selectedCols = NULL;
+ 			rte->modifiedCols = NULL;
+ 
+ 			/*
+ 			 * Update any varattnos in the main query that refer to this RTE,
+ 			 * using the entries from the RTE targetlist.
+ 			 */
+ 			parsetree = (Query *)
+ 				ReplaceVarsFromTargetList((Node *) parsetree,
+ 										  rt_index,
+ 										  0,
+ 										  rte,
+ 										  rte_targetlist,
+ 										  REPLACEVARS_REPORT_ERROR,
+ 										  0,
+ 										  &parsetree->hasSubLinks);
+ 			break;
+ 
+ 		case RTE_SUBQUERY:
+ 			/*
+ 			 * Build a new subquery that includes all the same columns as the
+ 			 * original subquery.
+ 			 */
+ 			foreach(cell, rte->subquery->targetList)
+ 			{
+ 				tle = (TargetEntry *) lfirst(cell);
+ 				var = makeVarFromTargetEntry(1, tle);
+ 
+ 				tle = makeTargetEntry((Expr *) var,
+ 									  list_length(targetlist) + 1,
+ 									  pstrdup(tle->resname),
+ 									  tle->resjunk);
+ 				targetlist = lappend(targetlist, tle);
+ 			}
+ 
+ 			subquery = makeNode(Query);
+ 			subquery->commandType = CMD_SELECT;
+ 			subquery->querySource = QSRC_INSTEAD_RULE;
+ 			subquery->targetList = targetlist;
+ 
+ 			subrte = makeNode(RangeTblEntry);
+ 			subrte->rtekind = RTE_SUBQUERY;
+ 			subrte->subquery = rte->subquery;
+ 			subrte->security_barrier = rte->security_barrier;
+ 			subrte->eref = copyObject(rte->eref);
+ 			subrte->inFromCl = true;
+ 			subquery->rtable = list_make1(subrte);
+ 
+ 			subrtr = makeNode(RangeTblRef);
+ 			subrtr->rtindex = 1;
+ 			subquery->jointree = makeFromExpr(list_make1(subrtr), qual);
+ 			subquery->hasSubLinks = checkExprHasSubLink(qual);
+ 
+ 			rte->subquery = subquery;
+ 			rte->security_barrier = true;
+ 
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "invalid range table entry for security barrier qual");
+ 	}
+ 
+ 	return parsetree;
+ }
+ 
+ 
+ /*
+  * rewriteSecurityQualsOnSubLink -
+  *	  Apply rewriteSecurityQuals() to each SubLink (subselect in expression)
+  *	  found in the given tree.
+  *
+  * NOTE: although this has the form of a walker, we cheat and modify the
+  * SubLink nodes in-place.  This is safe because we are not descending into
+  * subqueries, so no parts of the tree we are modifying are being traversed.
+  *
+  * Each SubLink subselect is replaced with a possibly-rewritten subquery.
+  */
+ static bool
+ rewriteSecurityQualsOnSubLink(Node *node, void *ctx)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 	if (IsA(node, SubLink))
+ 	{
+ 		SubLink	   *sub = (SubLink *) node;
+ 
+ 		sub->subselect = (Node *)
+ 			rewriteSecurityQuals((Query *) sub->subselect);
+ 		/* Fall through to process lefthand args of SubLink */
+ 	}
+ 
+ 	/*
+ 	 * Do NOT recurse into Query nodes, because rewriteSecurityQuals already
+ 	 * processed all subqueries in the rtable and cteList.
+ 	 */
+ 	return expression_tree_walker(node, rewriteSecurityQualsOnSubLink, ctx);
+ }
+ 
+ 
+ /*
+  * rewriteSecurityQuals -
+  *	  rewrites any security barrier quals on RTEs in the query, turning them
+  *	  into subqueries to allow the planner to enforce them before any user
+  *	  quals where necessary. Currently such security barrier quals can only
+  *	  have come from automatically updatable security barrier views.
+  *
+  * We do this at the end of the rewriting process (after any SELECT rules have
+  * been applied) so that the new security barrier subqueries wrap any remaining
+  * views after they are expanded.
+  *
+  * Any given RTE may have multiple security barrier quals in a list, from which
+  * we create a set of nested subqueries to isolate each security barrier from
+  * the others, providing protection against malicious user security barriers.
+  * The first item in the list represents the innermost subquery.
+  */
+ static Query *
+ rewriteSecurityQuals(Query *parsetree)
+ {
+ 	ListCell   *l;
+ 	int			rt_index;
+ 
+ 	/*
+ 	 * Process each RTE in the rtable list.  Security barrier quals are
+ 	 * initially only added to the result relation, but subsequent rules
+ 	 * may change that, so they may be anywhere.
+ 	 *
+ 	 * Note that this is deliberately not a foreach loop, since the whole
+ 	 * parsetree may be mutated each time through the loop.
+ 	 */
+ 	rt_index = 0;
+ 	while (rt_index < list_length(parsetree->rtable))
+ 	{
+ 		RangeTblEntry *rte;
+ 		bool		is_result_relation;
+ 		int			qual_idx;
+ 
+ 		++rt_index;
+ 		rte = rt_fetch(rt_index, parsetree->rtable);
+ 
+ 		if (rte->securityQuals == NIL)
+ 			continue;
+ 
+ 		/*
+ 		 * Ignore any RTEs that aren't used in the query (such RTEs may be
+ 		 * present for permissions checks).
+ 		 */
+ 		if (rt_index != parsetree->resultRelation &&
+ 			!rangeTableEntry_used((Node *) parsetree, rt_index, 0))
+ 			continue;
+ 
+ 		/*
+ 		 * Recursively process any security barrier quals in subquery RTEs
+ 		 * before processing any at this query level.
+ 		 */
+ 		if (rte->rtekind == RTE_SUBQUERY)
+ 			rte->subquery = rewriteSecurityQuals(rte->subquery);
+ 
+ 		/*
+ 		 * If this RTE is the target then we need to make a copy of it before
+ 		 * expanding it.  The unexpanded copy will become the new target, and
+ 		 * the expanded RTE will be the source of rows to update/delete
+ 		 * (cf. ApplyRetrieveRule).
+ 		 */
+ 		is_result_relation = rt_index == parsetree->resultRelation;
+ 		if (is_result_relation)
+ 		{
+ 			RangeTblEntry *newrte = copyObject(rte);
+ 			parsetree->rtable = lappend(parsetree->rtable, newrte);
+ 			parsetree->sourceRelation = parsetree->resultRelation;
+ 			parsetree->resultRelation = list_length(parsetree->rtable);
+ 
+ 			/*
+ 			 * Wipe out any copied security quals on the new target to prevent
+ 			 * infinite recursion.
+ 			 */
+ 			newrte->securityQuals = NIL;
+ 
+ 			/*
+ 			 * There's no need to do permissions checks twice, so wipe out the
+ 			 * permissions info for the original RTE (we prefer to keep the
+ 			 * bits set on the result RTE).
+ 			 */
+ 			rte->requiredPerms = 0;
+ 			rte->checkAsUser = InvalidOid;
+ 			rte->selectedCols = NULL;
+ 			rte->modifiedCols = NULL;
+ 
+ 			/*
+ 			 * For the most part, Vars referencing the original relation
+ 			 * should remain as as they are, meaning that they implicitly
+ 			 * represent OLD values.  But in the RETURNING list if any, we
+ 			 * want such Vars to represent NEW values, so change them to
+ 			 * reference the new RTE.
+ 			 *
+ 			 * Since ChangeVarNodes scribbles on the tree in-place, copy the
+ 			 * RETURNING list first for safety.
+ 			 */
+ 			parsetree->returningList = copyObject(parsetree->returningList);
+ 			ChangeVarNodes((Node *) parsetree->returningList, rt_index,
+ 						   parsetree->resultRelation, 0);
+ 		}
+ 
+ 		/*
+ 		 * Process each security qual in turn, starting with the innermost
+ 		 * one and working outwards.
+ 		 *
+ 		 * Note that we can't use a foreach loop here because the whole
+ 		 * parsetree may be mutated each time through the loop.  For the same
+ 		 * reason we must re-fetch the RTE each time.
+ 		 */
+ 		qual_idx = 0;
+ 		while (qual_idx < list_length(rte->securityQuals))
+ 		{
+ 			Node   *qual = (Node *) list_nth(rte->securityQuals, qual_idx);
+ 
+ 			ChangeVarNodes(qual, rt_index, 1, 0);
+ 			parsetree = rewriteSecurityQual(parsetree, rt_index,
+ 											is_result_relation, qual);
+ 
+ 			/* re-fetch the RTE in case it has been re-written */
+ 			rte = rt_fetch(rt_index, parsetree->rtable);
+ 
+ 			qual_idx++;
+ 		}
+ 		rte->securityQuals = NIL;
+ 	}
+ 
+ 	/* Recurse into subqueries in WITH */
+ 	foreach(l, parsetree->cteList)
+ 	{
+ 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
+ 
+ 		cte->ctequery = (Node *)
+ 			rewriteSecurityQuals((Query *) cte->ctequery);
+ 	}
+ 
+ 	/*
+ 	 * Recurse into sublink subqueries too, without descending into the rtable
+ 	 * or the cteList, which we have already processed.
+ 	 */
+ 	if (parsetree->hasSubLinks)
+ 		query_tree_walker(parsetree, rewriteSecurityQualsOnSubLink, NULL,
+ 						  QTW_IGNORE_RC_SUBQUERIES);
+ 
+ 	return parsetree;
+ }
+ 
+ 
+ /*
   * RewriteQuery -
   *	  rewrites the query and apply the rules again on the queries rewritten
   *
*************** QueryRewrite(Query *parsetree)
*** 3218,3224 ****
  	/*
  	 * Step 2
  	 *
! 	 * Apply all the RIR rules on each query
  	 *
  	 * This is also a handy place to mark each query with the original queryId
  	 */
--- 3675,3682 ----
  	/*
  	 * Step 2
  	 *
! 	 * Apply all the RIR rules on each query, and then expand any security
! 	 * quals that apply to RTEs in each query.
  	 *
  	 * This is also a handy place to mark each query with the original queryId
  	 */
*************** QueryRewrite(Query *parsetree)
*** 3228,3233 ****
--- 3686,3692 ----
  		Query	   *query = (Query *) lfirst(l);
  
  		query = fireRIRrules(query, NIL, false);
+ 		query = rewriteSecurityQuals(query);
  
  		query->queryId = input_query_id;
  
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index e5235cb..5e89651
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 110,115 ****
--- 110,118 ----
  	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR or a
  								 * non-optimizable statement */
  
+ 	int			sourceRelation;	/* rtable index of source relation for
+ 								 * UPDATE/DELETE; 0 otherwise */
+ 
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
  
*************** typedef struct RangeTblEntry
*** 801,806 ****
--- 804,810 ----
  	Oid			checkAsUser;	/* if valid, check access as this role */
  	Bitmapset  *selectedCols;	/* columns needing SELECT permission */
  	Bitmapset  *modifiedCols;	/* columns needing INSERT/UPDATE permission */
+ 	List	   *securityQuals;	/* any security barrier quals to apply */
  } RangeTblEntry;
  
  /*
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
new file mode 100644
index c725bba..b4d0af2
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
*************** CREATE VIEW rw_view14 AS SELECT ctid, a,
*** 22,33 ****
  CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
  CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
! CREATE VIEW ro_view18 WITH (security_barrier = true)
!   AS SELECT * FROM base_tbl; -- Security barrier views not updatable
! CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
! CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
! CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
   WHERE table_name LIKE E'r_\\_view%'
--- 22,31 ----
  CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
  CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
! CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
! CREATE VIEW ro_view19 AS SELECT * FROM seq; -- View based on a sequence
! CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
   WHERE table_name LIKE E'r_\\_view%'
*************** SELECT table_name, is_insertable_into
*** 44,50 ****
   ro_view19  | NO
   ro_view2   | NO
   ro_view20  | NO
-  ro_view21  | NO
   ro_view3   | NO
   ro_view4   | NO
   ro_view5   | NO
--- 42,47 ----
*************** SELECT table_name, is_insertable_into
*** 55,61 ****
   rw_view14  | YES
   rw_view15  | YES
   rw_view16  | YES
! (21 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
--- 52,58 ----
   rw_view14  | YES
   rw_view15  | YES
   rw_view16  | YES
! (20 rows)
  
  SELECT table_name, is_updatable, is_insertable_into
    FROM information_schema.views
*************** SELECT table_name, is_updatable, is_inse
*** 73,79 ****
   ro_view19  | NO           | NO
   ro_view2   | NO           | NO
   ro_view20  | NO           | NO
-  ro_view21  | NO           | NO
   ro_view3   | NO           | NO
   ro_view4   | NO           | NO
   ro_view5   | NO           | NO
--- 70,75 ----
*************** SELECT table_name, is_updatable, is_inse
*** 84,90 ****
   rw_view14  | YES          | YES
   rw_view15  | YES          | YES
   rw_view16  | YES          | YES
! (21 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
--- 80,86 ----
   rw_view14  | YES          | YES
   rw_view15  | YES          | YES
   rw_view16  | YES          | YES
! (20 rows)
  
  SELECT table_name, column_name, is_updatable
    FROM information_schema.columns
*************** SELECT table_name, column_name, is_updat
*** 103,125 ****
   ro_view17  | a             | NO
   ro_view17  | b             | NO
   ro_view18  | a             | NO
!  ro_view18  | b             | NO
!  ro_view19  | a             | NO
   ro_view2   | a             | NO
   ro_view2   | b             | NO
!  ro_view20  | sequence_name | NO
!  ro_view20  | last_value    | NO
!  ro_view20  | start_value   | NO
!  ro_view20  | increment_by  | NO
!  ro_view20  | max_value     | NO
!  ro_view20  | min_value     | NO
!  ro_view20  | cache_value   | NO
!  ro_view20  | log_cnt       | NO
!  ro_view20  | is_cycled     | NO
!  ro_view20  | is_called     | NO
!  ro_view21  | a             | NO
!  ro_view21  | b             | NO
!  ro_view21  | g             | NO
   ro_view3   | ?column?      | NO
   ro_view4   | count         | NO
   ro_view5   | a             | NO
--- 99,119 ----
   ro_view17  | a             | NO
   ro_view17  | b             | NO
   ro_view18  | a             | NO
!  ro_view19  | sequence_name | NO
!  ro_view19  | last_value    | NO
!  ro_view19  | start_value   | NO
!  ro_view19  | increment_by  | NO
!  ro_view19  | max_value     | NO
!  ro_view19  | min_value     | NO
!  ro_view19  | cache_value   | NO
!  ro_view19  | log_cnt       | NO
!  ro_view19  | is_cycled     | NO
!  ro_view19  | is_called     | NO
   ro_view2   | a             | NO
   ro_view2   | b             | NO
!  ro_view20  | a             | NO
!  ro_view20  | b             | NO
!  ro_view20  | g             | NO
   ro_view3   | ?column?      | NO
   ro_view4   | count         | NO
   ro_view5   | a             | NO
*************** SELECT table_name, column_name, is_updat
*** 140,146 ****
   rw_view16  | a             | YES
   rw_view16  | b             | YES
   rw_view16  | aa            | YES
! (48 rows)
  
  -- Read-only views
  DELETE FROM ro_view1;
--- 134,140 ----
   rw_view16  | a             | YES
   rw_view16  | b             | YES
   rw_view16  | aa            | YES
! (46 rows)
  
  -- Read-only views
  DELETE FROM ro_view1;
*************** INSERT INTO ro_view17 VALUES (3, 'ROW 3'
*** 268,291 ****
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! INSERT INTO ro_view18 VALUES (3, 'ROW 3');
! ERROR:  cannot insert into view "ro_view18"
! DETAIL:  Security-barrier views are not automatically updatable.
! HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! DELETE FROM ro_view19;
! ERROR:  cannot delete from view "ro_view19"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
! UPDATE ro_view20 SET max_value=1000;
! ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
! UPDATE ro_view21 SET b=upper(b);
! ERROR:  cannot update view "ro_view21"
  DETAIL:  Views that return set-returning functions are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 17 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
--- 262,281 ----
  ERROR:  cannot insert into view "ro_view1"
  DETAIL:  Views containing DISTINCT are not automatically updatable.
  HINT:  To enable inserting into the view, provide an INSTEAD OF INSERT trigger or an unconditional ON INSERT DO INSTEAD rule.
! DELETE FROM ro_view18;
! ERROR:  cannot delete from view "ro_view18"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable deleting from the view, provide an INSTEAD OF DELETE trigger or an unconditional ON DELETE DO INSTEAD rule.
! UPDATE ro_view19 SET max_value=1000;
! ERROR:  cannot update view "ro_view19"
  DETAIL:  Views that do not select from a single table or view are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
! UPDATE ro_view20 SET b=upper(b);
! ERROR:  cannot update view "ro_view20"
  DETAIL:  Views that return set-returning functions are not automatically updatable.
  HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.
  DROP TABLE base_tbl CASCADE;
! NOTICE:  drop cascades to 16 other objects
  DETAIL:  drop cascades to view ro_view1
  drop cascades to view ro_view17
  drop cascades to view ro_view2
*************** drop cascades to view ro_view11
*** 299,311 ****
  drop cascades to view ro_view13
  drop cascades to view rw_view15
  drop cascades to view rw_view16
! drop cascades to view ro_view18
! drop cascades to view ro_view21
  drop cascades to view ro_view4
  drop cascades to view rw_view14
! DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
! NOTICE:  drop cascades to view ro_view20
  -- simple updatable view
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
--- 289,300 ----
  drop cascades to view ro_view13
  drop cascades to view rw_view15
  drop cascades to view rw_view16
! drop cascades to view ro_view20
  drop cascades to view ro_view4
  drop cascades to view rw_view14
! DROP VIEW ro_view10, ro_view12, ro_view18;
  DROP SEQUENCE seq CASCADE;
! NOTICE:  drop cascades to view ro_view19
  -- simple updatable view
  CREATE TABLE base_tbl (a int PRIMARY KEY, b text DEFAULT 'Unspecified');
  INSERT INTO base_tbl SELECT i, 'Row ' || i FROM generate_series(-2, 2) g(i);
*************** INSERT INTO rw_view2 VALUES (2,3); -- ok
*** 1739,1742 ****
--- 1728,1927 ----
  DROP TABLE base_tbl CASCADE;
  NOTICE:  drop cascades to 2 other objects
  DETAIL:  drop cascades to view rw_view1
+ drop cascades to view rw_view2
+ -- security barrier view
+ CREATE TABLE base_tbl (person text, visibility text);
+ INSERT INTO base_tbl VALUES ('Tom', 'public'),
+                             ('Dick', 'private'),
+                             ('Harry', 'public');
+ CREATE VIEW rw_view1 AS
+   SELECT person FROM base_tbl WHERE visibility = 'public';
+ CREATE FUNCTION snoop(val text)
+ RETURNS boolean AS
+ $$
+ BEGIN
+   RAISE NOTICE 'snooped value: %', val;
+   RETURN true;
+ END;
+ $$
+ LANGUAGE plpgsql COST 0.000001;
+ SELECT * FROM rw_view1 WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Dick
+ NOTICE:  snooped value: Harry
+  person 
+ --------
+  Tom
+  Harry
+ (2 rows)
+ 
+ UPDATE rw_view1 SET person=person WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Dick
+ NOTICE:  snooped value: Harry
+ DELETE FROM rw_view1 WHERE NOT snoop(person);
+ NOTICE:  snooped value: Dick
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ ALTER VIEW rw_view1 SET (security_barrier = true);
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name = 'rw_view1';
+  table_name | is_insertable_into 
+ ------------+--------------------
+  rw_view1   | YES
+ (1 row)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name = 'rw_view1';
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  rw_view1   | YES          | YES
+ (1 row)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name = 'rw_view1'
+  ORDER BY ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  rw_view1   | person      | YES
+ (1 row)
+ 
+ SELECT * FROM rw_view1 WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+  person 
+ --------
+  Tom
+  Harry
+ (2 rows)
+ 
+ UPDATE rw_view1 SET person=person WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ DELETE FROM rw_view1 WHERE NOT snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
+                   QUERY PLAN                   
+ -----------------------------------------------
+  Subquery Scan on rw_view1
+    Filter: snoop(rw_view1.person)
+    ->  Seq Scan on base_tbl
+          Filter: (visibility = 'public'::text)
+ (4 rows)
+ 
+ EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
+                         QUERY PLAN                         
+ -----------------------------------------------------------
+  Update on base_tbl base_tbl_1
+    ->  Subquery Scan on base_tbl
+          Filter: snoop(base_tbl.person)
+          ->  LockRows
+                ->  Seq Scan on base_tbl base_tbl_2
+                      Filter: (visibility = 'public'::text)
+ (6 rows)
+ 
+ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+                         QUERY PLAN                         
+ -----------------------------------------------------------
+  Delete on base_tbl base_tbl_1
+    ->  Subquery Scan on base_tbl
+          Filter: (NOT snoop(base_tbl.person))
+          ->  LockRows
+                ->  Seq Scan on base_tbl base_tbl_2
+                      Filter: (visibility = 'public'::text)
+ (6 rows)
+ 
+ -- security barrier view on top of security barrier view
+ CREATE VIEW rw_view2 WITH (security_barrier = true) AS
+   SELECT * FROM rw_view1 WHERE snoop(person);
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name = 'rw_view2';
+  table_name | is_insertable_into 
+ ------------+--------------------
+  rw_view2   | YES
+ (1 row)
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name = 'rw_view2';
+  table_name | is_updatable | is_insertable_into 
+ ------------+--------------+--------------------
+  rw_view2   | YES          | YES
+ (1 row)
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name = 'rw_view2'
+  ORDER BY ordinal_position;
+  table_name | column_name | is_updatable 
+ ------------+-------------+--------------
+  rw_view2   | person      | YES
+ (1 row)
+ 
+ SELECT * FROM rw_view2 WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ NOTICE:  snooped value: Harry
+  person 
+ --------
+  Tom
+  Harry
+ (2 rows)
+ 
+ UPDATE rw_view2 SET person=person WHERE snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ NOTICE:  snooped value: Harry
+ DELETE FROM rw_view2 WHERE NOT snoop(person);
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Tom
+ NOTICE:  snooped value: Harry
+ NOTICE:  snooped value: Harry
+ EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
+                      QUERY PLAN                      
+ -----------------------------------------------------
+  Subquery Scan on rw_view2
+    Filter: snoop(rw_view2.person)
+    ->  Subquery Scan on rw_view1
+          Filter: snoop(rw_view1.person)
+          ->  Seq Scan on base_tbl
+                Filter: (visibility = 'public'::text)
+ (6 rows)
+ 
+ EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Update on base_tbl base_tbl_1
+    ->  Subquery Scan on base_tbl
+          Filter: snoop(base_tbl.person)
+          ->  Subquery Scan on base_tbl_2
+                Filter: snoop(base_tbl_2.person)
+                ->  LockRows
+                      ->  Seq Scan on base_tbl base_tbl_3
+                            Filter: (visibility = 'public'::text)
+ (8 rows)
+ 
+ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+                            QUERY PLAN                            
+ -----------------------------------------------------------------
+  Delete on base_tbl base_tbl_1
+    ->  Subquery Scan on base_tbl
+          Filter: (NOT snoop(base_tbl.person))
+          ->  Subquery Scan on base_tbl_2
+                Filter: snoop(base_tbl_2.person)
+                ->  LockRows
+                      ->  Seq Scan on base_tbl base_tbl_3
+                            Filter: (visibility = 'public'::text)
+ (8 rows)
+ 
+ DROP TABLE base_tbl CASCADE;
+ NOTICE:  drop cascades to 2 other objects
+ DETAIL:  drop cascades to view rw_view1
  drop cascades to view rw_view2
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
new file mode 100644
index a77cf19..6b352c1
*** a/src/test/regress/sql/updatable_views.sql
--- b/src/test/regress/sql/updatable_views.sql
*************** CREATE VIEW rw_view14 AS SELECT ctid, a,
*** 25,36 ****
  CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
  CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
! CREATE VIEW ro_view18 WITH (security_barrier = true)
!   AS SELECT * FROM base_tbl; -- Security barrier views not updatable
! CREATE VIEW ro_view19 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
! CREATE VIEW ro_view20 AS SELECT * FROM seq; -- View based on a sequence
! CREATE VIEW ro_view21 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
--- 25,34 ----
  CREATE VIEW rw_view15 AS SELECT a, upper(b) FROM base_tbl; -- Expression/function may be part of an updatable view
  CREATE VIEW rw_view16 AS SELECT a, b, a AS aa FROM base_tbl; -- Repeated column may be part of an updatable view
  CREATE VIEW ro_view17 AS SELECT * FROM ro_view1; -- Base relation not updatable
! CREATE VIEW ro_view18 AS SELECT * FROM (VALUES(1)) AS tmp(a); -- VALUES in rangetable
  CREATE SEQUENCE seq;
! CREATE VIEW ro_view19 AS SELECT * FROM seq; -- View based on a sequence
! CREATE VIEW ro_view20 AS SELECT a, b, generate_series(1, a) g FROM base_tbl; -- SRF in targetlist not supported
  
  SELECT table_name, is_insertable_into
    FROM information_schema.tables
*************** SELECT * FROM base_tbl;
*** 87,99 ****
  DELETE FROM rw_view16 WHERE a=-3; -- should be OK
  -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
! INSERT INTO ro_view18 VALUES (3, 'ROW 3');
! DELETE FROM ro_view19;
! UPDATE ro_view20 SET max_value=1000;
! UPDATE ro_view21 SET b=upper(b);
  
  DROP TABLE base_tbl CASCADE;
! DROP VIEW ro_view10, ro_view12, ro_view19;
  DROP SEQUENCE seq CASCADE;
  
  -- simple updatable view
--- 85,96 ----
  DELETE FROM rw_view16 WHERE a=-3; -- should be OK
  -- Read-only views
  INSERT INTO ro_view17 VALUES (3, 'ROW 3');
! DELETE FROM ro_view18;
! UPDATE ro_view19 SET max_value=1000;
! UPDATE ro_view20 SET b=upper(b);
  
  DROP TABLE base_tbl CASCADE;
! DROP VIEW ro_view10, ro_view12, ro_view18;
  DROP SEQUENCE seq CASCADE;
  
  -- simple updatable view
*************** CREATE VIEW rw_view2 AS
*** 828,830 ****
--- 825,902 ----
    SELECT * FROM rw_view1 WHERE a > b WITH LOCAL CHECK OPTION;
  INSERT INTO rw_view2 VALUES (2,3); -- ok, but not in view (doesn't fail rw_view2's check)
  DROP TABLE base_tbl CASCADE;
+ 
+ -- security barrier view
+ 
+ CREATE TABLE base_tbl (person text, visibility text);
+ INSERT INTO base_tbl VALUES ('Tom', 'public'),
+                             ('Dick', 'private'),
+                             ('Harry', 'public');
+ 
+ CREATE VIEW rw_view1 AS
+   SELECT person FROM base_tbl WHERE visibility = 'public';
+ 
+ CREATE FUNCTION snoop(val text)
+ RETURNS boolean AS
+ $$
+ BEGIN
+   RAISE NOTICE 'snooped value: %', val;
+   RETURN true;
+ END;
+ $$
+ LANGUAGE plpgsql COST 0.000001;
+ 
+ SELECT * FROM rw_view1 WHERE snoop(person);
+ UPDATE rw_view1 SET person=person WHERE snoop(person);
+ DELETE FROM rw_view1 WHERE NOT snoop(person);
+ 
+ ALTER VIEW rw_view1 SET (security_barrier = true);
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name = 'rw_view1';
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name = 'rw_view1';
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name = 'rw_view1'
+  ORDER BY ordinal_position;
+ 
+ SELECT * FROM rw_view1 WHERE snoop(person);
+ UPDATE rw_view1 SET person=person WHERE snoop(person);
+ DELETE FROM rw_view1 WHERE NOT snoop(person);
+ 
+ EXPLAIN (costs off) SELECT * FROM rw_view1 WHERE snoop(person);
+ EXPLAIN (costs off) UPDATE rw_view1 SET person=person WHERE snoop(person);
+ EXPLAIN (costs off) DELETE FROM rw_view1 WHERE NOT snoop(person);
+ 
+ -- security barrier view on top of security barrier view
+ 
+ CREATE VIEW rw_view2 WITH (security_barrier = true) AS
+   SELECT * FROM rw_view1 WHERE snoop(person);
+ 
+ SELECT table_name, is_insertable_into
+   FROM information_schema.tables
+  WHERE table_name = 'rw_view2';
+ 
+ SELECT table_name, is_updatable, is_insertable_into
+   FROM information_schema.views
+  WHERE table_name = 'rw_view2';
+ 
+ SELECT table_name, column_name, is_updatable
+   FROM information_schema.columns
+  WHERE table_name = 'rw_view2'
+  ORDER BY ordinal_position;
+ 
+ SELECT * FROM rw_view2 WHERE snoop(person);
+ UPDATE rw_view2 SET person=person WHERE snoop(person);
+ DELETE FROM rw_view2 WHERE NOT snoop(person);
+ 
+ EXPLAIN (costs off) SELECT * FROM rw_view2 WHERE snoop(person);
+ EXPLAIN (costs off) UPDATE rw_view2 SET person=person WHERE snoop(person);
+ EXPLAIN (costs off) DELETE FROM rw_view2 WHERE NOT snoop(person);
+ 
+ DROP TABLE base_tbl CASCADE;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 09:02                                                                 ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2013-11-06 09:23                                                                   ` Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 09:55                                                                     ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2013-11-06 09:23 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 11/06/2013 05:02 PM, Dean Rasheed wrote:

> The basic idea is to have rewriteTargetView() collect up any quals
> from SB views in a new list on the target RTE, instead of adding them
> to the main query's predicates (it needs to be a list of SB quals, in
> case there are SB views on top of other SB views, in which case they
> need to be kept separate from one another). Then at the end of the
> rewriting process (after any views referenced in the SB quals have
> been expanded), a new piece of code kicks in to process any RTEs with
> SB quals, turning them into (possibly nested) subquery RTEs.

That makes sense, though presumably you face the same problem that the
existing RLS code does with references to system columns that don't
normally exist in subqueries?

Since this happens during query rewrite, what prevents the optimizer
from pushing outer quals down into the subqueries?

> The complication is that the query's resultRelation RTE mustn't be a
> subquery.

I think this is what Robert was alluding to earlier with his comments
about join relations:

____
Robert Haas wrote:
> I don't really see why.  AIUI, the ModifyTable node just needs to get
> the right TIDs.  It's not like that has to be stacked directly on top
> of a scan; indeed, in cases like UPDATE .. FROM and DELETE .. USING it
> already isn't.  Maybe there's some reason why the intervening level
> can be a Join but not a  SubqueryScan, but if so I'd expect we could
> find some way of lifting that limitation without suffering too much
> pain.
(http://www.postgresql.org/message-id/CA+TgmoYr1PHw3X9vnVuWDcfXkzK2p_jhtWc0fV2Q58NEgcxyTA@mail.gmail....)
____


Maybe we just need to make a subquery scan a valid target for an update,
so those fixups aren't required anymore?

> This is handled this in a similar way to the
> trigger-updatable views code, producing 2 RTEs --- the resultRelation
> RTE becomes a direct reference to the base relation, and a separate
> subquery RTE acts as the source of rows to update.

Some of the complexity of the current RLS code is caused by the need to
do similar fixups to handle the case where the input relation isn't the
same as the target relation, but is a subquery over it instead.

> Anyway, feel free to do what you like with this. I wasn't planning on
> submitting it to the next commitfest myself, because my non-PG
> workload is too high, and I don't expect to get much time to hack on
> postgresql during the next couple of months.

Thanks for sending what you have. It's informative, and it shows that
some of the same issues must be solved for writable security barrier
views and for RLS.


-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 09:02                                                                 ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-11-06 09:23                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-06 09:55                                                                     ` Dean Rasheed <dean.a.rasheed@gmail.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Dean Rasheed @ 2013-11-06 09:55 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 6 November 2013 09:23, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 11/06/2013 05:02 PM, Dean Rasheed wrote:
>
>> The basic idea is to have rewriteTargetView() collect up any quals
>> from SB views in a new list on the target RTE, instead of adding them
>> to the main query's predicates (it needs to be a list of SB quals, in
>> case there are SB views on top of other SB views, in which case they
>> need to be kept separate from one another). Then at the end of the
>> rewriting process (after any views referenced in the SB quals have
>> been expanded), a new piece of code kicks in to process any RTEs with
>> SB quals, turning them into (possibly nested) subquery RTEs.
>
> That makes sense, though presumably you face the same problem that the
> existing RLS code does with references to system columns that don't
> normally exist in subqueries?
>

Yeah, that feels like an ugly hack.


> Since this happens during query rewrite, what prevents the optimizer
> from pushing outer quals down into the subqueries?
>

The subquery RTE is marked with the security_barrier flag, which
prevents quals from being pushed down in the presence of leaky
functions (see set_subquery_pathlist).


>> The complication is that the query's resultRelation RTE mustn't be a
>> subquery.
>
> I think this is what Robert was alluding to earlier with his comments
> about join relations:
>
> ____
> Robert Haas wrote:
>> I don't really see why.  AIUI, the ModifyTable node just needs to get
>> the right TIDs.  It's not like that has to be stacked directly on top
>> of a scan; indeed, in cases like UPDATE .. FROM and DELETE .. USING it
>> already isn't.  Maybe there's some reason why the intervening level
>> can be a Join but not a  SubqueryScan, but if so I'd expect we could
>> find some way of lifting that limitation without suffering too much
>> pain.
> (http://www.postgresql.org/message-id/CA+TgmoYr1PHw3X9vnVuWDcfXkzK2p_jhtWc0fV2Q58NEgcxyTA@mail.gmail....)
> ____
>

Yeah. We already do a similar thing for trigger-updatable views. So
with this approach you end up with similar plans, for example:

Update on base_tbl
  ->  Subquery Scan on base_tbl ...


>
> Maybe we just need to make a subquery scan a valid target for an update,
> so those fixups aren't required anymore?
>

Possibly. That feels like it would require much more extensive surgery
on the planner though. I've not explored that idea, but I suspect it
would quickly turn into a whole new can of worms.


>> This is handled this in a similar way to the
>> trigger-updatable views code, producing 2 RTEs --- the resultRelation
>> RTE becomes a direct reference to the base relation, and a separate
>> subquery RTE acts as the source of rows to update.
>
> Some of the complexity of the current RLS code is caused by the need to
> do similar fixups to handle the case where the input relation isn't the
> same as the target relation, but is a subquery over it instead.
>
>> Anyway, feel free to do what you like with this. I wasn't planning on
>> submitting it to the next commitfest myself, because my non-PG
>> workload is too high, and I don't expect to get much time to hack on
>> postgresql during the next couple of months.
>
> Thanks for sending what you have. It's informative, and it shows that
> some of the same issues must be solved for writable security barrier
> views and for RLS.
>

Agreed. I'm not sure what the best way to fix those issues is though.
The currently proposed approach feels pretty ugly, but I can't see a
better way at the moment.

Regards,
Dean


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 09:02                                                                 ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2013-11-06 18:14                                                                   ` Josh Berkus <josh@agliodbs.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Josh Berkus @ 2013-11-06 18:14 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

All,

Just a comment: I'm really glad to see the serious work on this.  If RLS
doesn't make it into 9.4, it'll be because the problems of RLS are
fundamentally unsolvable, not because we didn't give it our best.  Great
work, all!

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-06 19:12                                                                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-11-07 10:11                                                                   ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  3 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-11-06 19:12 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/11/6 Craig Ringer <craig@2ndquadrant.com>:
> On 11/05/2013 09:36 PM, Robert Haas wrote:
>> I haven't studied this patch in detail, but I see why there's some
>> unhappiness about that code: it's an RLS-specific kluge.  Just
>> shooting from the hip here, maybe we should attack the problem of
>> making security-barrier views updatable first, as a separate patch.
>
> That's the approach I've been considering. There are a few wrinkles with
> it, though:
>
> (a) Updatable views are implemented in the rewriter, not the planner.
> The rewriter is not re-run when plans are invalidated or when the
> session authorization changes, etc. This means that we can't simply omit
> the RLS predicate for superuser because the same rewritten parse tree
> might get used for both superuser and non-superuser queries.
>
> Options:
>
> * Store the before-rewrite parse tree when RLS is discovered on one of
> the rels in the tree. Re-run the rewriter when re-planning. Ensure a
> change in current user always invalidates plans.
>
> * Declare that it's not legal to run a query originally parsed and
> rewritten as superuser as a non-superuser or vice versa. This would
> cause a great deal of pain with PL/PgSQL.
>
> * Always add the RLS predicate and solve the problem of reliably
> short-circuiting the user-supplied predicate for RLS-exempt users.  We'd
> need a way to allow direct (not query-based) COPY TO for tables with RLS
> applied, preventing the rewriting of direct table access into subqueries
> for COPY, but since we never save plans for COPY that may be fine.
>
> * ... ?
>
How about an idea that uses two different type of rules: the existing one
is expanded prior to planner stage as we are doing now, and the newer
one is expanded on the head of planner stage.
The argument of planner() is still parse tree, so it seems to me here is
no serious problem to call rewriter again to handle second stage rules.
If we go on this approach, ALTER TABLE ... SET ROW SECURITY
will become a synonym to declare a rule with special attribute.

> (b) Inheritance is a problem when RLS is done in the rewriter. As I
> understood it from Kohei KaiGai's description to me earlier, there was a
> strong preference on -hackers to enforce RLS predicates for child and
> parent tables completely independently. That's how RLS currently works,
> but it might be hard to get the same effect when applying RLS in the
> rewriter. We'd need to solve that, or redefine RLS's behaviour so that
> the predicate on a parent table applies to any child tables too.
> Personally I'd prefer the latter.
>
I'm not certain whether it was a "strong preference", even though I followed
the consensus at that time. So, I think it makes sense to discuss how RLS
policy shall be enforced on the child tables.
As long as we can have consistent view on child tables even if it is referenced
without parent tables, I don't have any arguments to your preference.
Also, it makes implementation simple than the approach I tried to have; that
enforces RLS policy of tables individually, because of utilization of existing
rule mechanism.
It is not difficult to enforce parent's RLS policy on the child relation even if
it is referenced individually. All we need to do special is append RLS policy
of its parent, not only child's one, if referenced table has parent.

> (c) RLS might interact differently with rules declared on tables if
> implemented in the rewriter, so some investigation into that would be
> needed.
>
>> I
>> would think that if we make that work, this will also work without,
>> hopefully, any special hackery.  And we'd get a separate,
>> independently useful feature out of it, too.
>
> I tend to agree. I'm just a bit concerned about dealing with the issues
> around RLS-exempt operations and users.
>
> --
>  Craig Ringer                   http://www.2ndQuadrant.com/
>  PostgreSQL Development, 24x7 Support, Training & Services



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 19:12                                                                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-11-07 10:11                                                                   ` Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-11-08 02:17                                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Dean Rasheed @ 2013-11-07 10:11 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Craig Ringer <craig@2ndquadrant.com>; Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 6 November 2013 19:12, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> 2013/11/6 Craig Ringer <craig@2ndquadrant.com>:
>> On 11/05/2013 09:36 PM, Robert Haas wrote:
>>> I haven't studied this patch in detail, but I see why there's some
>>> unhappiness about that code: it's an RLS-specific kluge.  Just
>>> shooting from the hip here, maybe we should attack the problem of
>>> making security-barrier views updatable first, as a separate patch.
>>
>> That's the approach I've been considering. There are a few wrinkles with
>> it, though:
>>
>> (a) Updatable views are implemented in the rewriter, not the planner.
>> The rewriter is not re-run when plans are invalidated or when the
>> session authorization changes, etc. This means that we can't simply omit
>> the RLS predicate for superuser because the same rewritten parse tree
>> might get used for both superuser and non-superuser queries.
>>
>> Options:
>>
>> * Store the before-rewrite parse tree when RLS is discovered on one of
>> the rels in the tree. Re-run the rewriter when re-planning. Ensure a
>> change in current user always invalidates plans.
>>
>> * Declare that it's not legal to run a query originally parsed and
>> rewritten as superuser as a non-superuser or vice versa. This would
>> cause a great deal of pain with PL/PgSQL.
>>
>> * Always add the RLS predicate and solve the problem of reliably
>> short-circuiting the user-supplied predicate for RLS-exempt users.  We'd
>> need a way to allow direct (not query-based) COPY TO for tables with RLS
>> applied, preventing the rewriting of direct table access into subqueries
>> for COPY, but since we never save plans for COPY that may be fine.
>>
>> * ... ?
>>
> How about an idea that uses two different type of rules: the existing one
> is expanded prior to planner stage as we are doing now, and the newer
> one is expanded on the head of planner stage.
> The argument of planner() is still parse tree, so it seems to me here is
> no serious problem to call rewriter again to handle second stage rules.
> If we go on this approach, ALTER TABLE ... SET ROW SECURITY
> will become a synonym to declare a rule with special attribute.
>

I don't really get this part of the discussion. Why would you want to
make updatable SB views do any of that? With RLS on tables, there is
only one object in play - the table itself. So I can see that there is
this requirement for certain privileged users to be able to bypass the
RLS quals, and hence the need to invalidate cached plans.

With SB views, however, you can have multiple SB views on top of the
same base table, each giving different users access to different
subsets of the data, and controlled by suitable GRANTs, and suitably
privileged users can be given direct access to the base table. This
also gives greater flexibility than the superuser/non-superuser
distinction being discussed here.

I don't think a view should ever show different data to different
users (unless it has been deliberately set up to do so) because that
would likely lead to confusion. Is there some other use-case that I'm
missing here?

Regards,
Dean


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-06 19:12                                                                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-11-07 10:11                                                                   ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
@ 2013-11-08 02:17                                                                     ` Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Craig Ringer @ 2013-11-08 02:17 UTC (permalink / raw)
  To: Dean Rasheed <dean.a.rasheed@gmail.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 11/07/2013 06:11 PM, Dean Rasheed wrote:
> I don't really get this part of the discussion. Why would you want to
> make updatable SB views do any of that?

I don't, especially. If we're going to use updatable SB views as the
basis for RLS then we need the option to skip adding the qual for superuser.

That might be done outside the SB code its self, by not invoking the SB
view rewrite on the base rel when we see we're running as an RLS-exempt
user. It's all about dealing with the re-plan problem really.

> With SB views, however, you can have multiple SB views on top of the
> same base table, each giving different users access to different
> subsets of the data, and controlled by suitable GRANTs, and suitably
> privileged users can be given direct access to the base table. This
> also gives greater flexibility than the superuser/non-superuser
> distinction being discussed here.


That's a thought.

Can we munge what the planner sees in pg_class instead? So when we see a
ref to an RLS relation we transparently substitute the oid for a hidden
SB view over the relation instead. For RLS exempt users we omit that
substitution.

Since the lookups and view subs are done in the rewrite phase it
probably doesn't help us much, but it'd get rid of the need to play
about with substituting a RTE_RELATION with an RTE_SUBQUERY dynamically
during rewrite.

> I don't think a view should ever show different data to different
> users (unless it has been deliberately set up to do so) because that
> would likely lead to confusion. Is there some other use-case that I'm
> missing here?

The main concern is pg_dump - it's important that dumps be able to take
a complete copy without relying on hacks or bug-free user-written RLS quals.

Highly privileged users should also be exempt from RLS so they don't
invoke untrusted functions that're part of RLS quals written by
lower-rights users.

This isn't really "superuser" vs "not superuser. In fact we'll want a
new right that controls whether RLS can be bypassed, and another that
controls the ability to set RLS rights on tables. Both of those would be
superuser only by default, but could be delegated.

(Note: It's important that table owners _not_ get the right to set RLS
on tables by default for security reasons. I'll explain in more detail
later.)


-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-07 13:47                                                                 ` Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  3 siblings, 1 reply; 165+ messages in thread

From: Greg Stark @ 2013-11-07 13:47 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Nov 6, 2013 at 6:38 AM, Craig Ringer <craig@2ndquadrant.com> wrote:

> That's the approach I've been considering. There are a few wrinkles with
> it, though:
>
> (a) Updatable views are implemented in the rewriter, not the planner.
> The rewriter is not re-run when plans are invalidated or when the
> session authorization changes, etc. This means that we can't simply omit
> the RLS predicate for superuser because the same rewritten parse tree
> might get used for both superuser and non-superuser queries.
>

Incidentally I still feel this is at root the problem with updateable views
in general. I know it's a bit off to be tossing in concerns from the peanut
gallery when I'm not actually offering to work on it and others are having
putting in serious efforts in this area and having some success. So take
this for what it's worth...

I think the right approach for updateable views would be to support a
syntax like this in the planner:

UPDATE (select * from tab1,tab2 ...) WHERE tab1.id = .. SET ...

Then updateable views would just rewrite the query mechanically the way
regular views work by substituting the view definition in place of the view
name. Since all the work would be done in the planner it would have access
to the same kinds of information that regular join planning etc have.

I'm not sure if this solves all the problems with RLS but it would solve
the concern about plan invalidations and I think it would make it simpler
to reason about security rules that are otherwise happening at plan time.

-- 
greg

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
@ 2013-11-08 02:08                                                                   ` Craig Ringer <craig@2ndquadrant.com>
  2013-11-11 21:40                                                                     ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  0 siblings, 2 replies; 165+ messages in thread

From: Craig Ringer @ 2013-11-08 02:08 UTC (permalink / raw)
  To: Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On 11/07/2013 09:47 PM, Greg Stark wrote:

> Incidentally I still feel this is at root the problem with updateable
> views in general. I know it's a bit off to be tossing in concerns from
> the peanut gallery when I'm not actually offering to work on it and
> others are having putting in serious efforts in this area and having
> some success. So take this for what it's worth...

Frankly, the peanut gallery input can be quite handy. It's easy to get
so stuck in the way you've seen it thought about already that you don't
see other ways to view it. Plus, sometimes the peanut gallery becomes
the "oh, I don't like this at all" crowd when commit time is
approaching, so early comments are better than no comments then last
minute complaints.

> I think the right approach for updateable views would be to support a
> syntax like this in the planner:
> 
> UPDATE (select * from tab1,tab2 ...) WHERE tab1.id <http://tab1.id; = ..
> SET ...

I want to support that for rewritten parse trees, and therefore (because
of recursive rewrite) in pre-rewrite parse trees. It's exactly what's
needed to make this sane, IMO, and I think this is what Robert was
suggesting with making UPDATE capable of dealing with operating directly
on a subquery scan.

I'm not at all convinced it should be exposed to the user and accepted
by the parser as SQL, but I don't know if that's what you were suggesting.

Robert? Is this what you meant? If so, any chance you can point a
planner neophyte like me in vaguely the right direction?

> I'm not sure if this solves all the problems with RLS but it would solve
> the concern about plan invalidations and I think it would make it
> simpler to reason about security rules that are otherwise happening at
> plan time.

I don't think it'd help with plan invalidations. View expansion happens
at rewrite time, and we don't re-run the rewriter on the original parse
tree when we invalidate plans and re-plan. The current patch entirely
elides the RLS quals and subquery when running as superuser and that
won't work with view expansion at the rewrite stage.

So we still have that to deal with, and need to handle a few side issues
with portals and user id changes etc, but the biggest issue is coming up
with an acceptable way to update a security barrier view or subquery.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-11 21:40                                                                     ` Robert Haas <robertmhaas@gmail.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Robert Haas @ 2013-11-11 21:40 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Greg Stark <stark@mit.edu>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Thu, Nov 7, 2013 at 9:08 PM, Craig Ringer <craig@2ndquadrant.com> wrote:
> On 11/07/2013 09:47 PM, Greg Stark wrote:
>> Incidentally I still feel this is at root the problem with updateable
>> views in general. I know it's a bit off to be tossing in concerns from
>> the peanut gallery when I'm not actually offering to work on it and
>> others are having putting in serious efforts in this area and having
>> some success. So take this for what it's worth...
>
> Frankly, the peanut gallery input can be quite handy. It's easy to get
> so stuck in the way you've seen it thought about already that you don't
> see other ways to view it. Plus, sometimes the peanut gallery becomes
> the "oh, I don't like this at all" crowd when commit time is
> approaching, so early comments are better than no comments then last
> minute complaints.
>
>> I think the right approach for updateable views would be to support a
>> syntax like this in the planner:
>>
>> UPDATE (select * from tab1,tab2 ...) WHERE tab1.id <http://tab1.id; = ..
>> SET ...
>
> I want to support that for rewritten parse trees, and therefore (because
> of recursive rewrite) in pre-rewrite parse trees. It's exactly what's
> needed to make this sane, IMO, and I think this is what Robert was
> suggesting with making UPDATE capable of dealing with operating directly
> on a subquery scan.
>
> I'm not at all convinced it should be exposed to the user and accepted
> by the parser as SQL, but I don't know if that's what you were suggesting.
>
> Robert? Is this what you meant? If so, any chance you can point a
> planner neophyte like me in vaguely the right direction?

I haven't studied this issue well enough to know what's really needed
here, but Dean Rasheed's approach sounded like a promising tack to me.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-12-14 03:24                                                                     ` Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 04:56                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Gregory Smith @ 2013-12-14 03:24 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

While Craig has been soldiering onward to the write side of things, I've 
been working with some groups who are evaluating if the RLS feature is 
good enough for switching some real world apps over to use PostgreSQL.  
Amazingly I can even say that these are applications involving the US 
Department of Defense, and they're going well so far.  There has been 
some concern on this list as to whether this feature is being evaluated 
by people who are serious about real-world database security issues.  
The crowd I'm talking to now sure is.  There's some interesting bad 
news, like how we're going to have to completely refactor all the "if 
(!superuser())" shortcuts into real permissions one day to make them 
happy.  But for the most part the prototype conversions have been 
working out.  The RLS feature set available with the CF submission is 
good enough for those projects to continue exploring PostgreSQL.  I feel 
good now that if the code issues around what's already been developed 
are sorted out to commit quality, the users really will queue once this 
hits.

== Review demo code ==

Attached is a small demo piece built by my new co-worker Jeff McCormick 
that I've been hacking lightly on.  The idea was to get something a step 
beyond trivial that maps into real-world need, well enough that we could 
get people to agree it was usefully representative.  What they did is 
take the sort of security levels these apps have, which often includes 
two different criteria for each row, and mapped that into what the RLS 
submission supports. There are a set of roles for the permission to see 
various shapes (triangle/circle/square/heart) and colors, and then 
people can see a row only if they have both color and shape 
permissions.  The part that I thought was cute when I first read it is 
how this query looks up data in the catalog to accomplish that:

ALTER TABLE colors_shapes
   SET ROW SECURITY FOR ALL TO (
   color IN (
     SELECT permission_role.rolname FROM pg_roles active_role, 
pg_auth_members, pg_roles permission_role WHERE
       active_role.rolname = current_user AND
       active_role.oid = pg_auth_members.member AND
       permission_role.oid = pg_auth_members.roleid
     )
   AND shape IN (
     SELECT permission_role.rolname FROM pg_roles active_role, 
pg_auth_members, pg_roles permission_role WHERE
       active_role.rolname = current_user AND
       active_role.oid = pg_auth_members.member AND
       permission_role.oid = pg_auth_members.roleid
     )
);

Insert some data that looks like this:

  id |    color    |     shape
----+-------------+---------------
   1 | blue_role   | triangle_role
   2 | red_role    | heart_role
   3 | purple_role | circle_role

And then users can see rows only if they have a pair of matches on both 
those role sets.  The fact that you can just plug in whatever you want 
into SET ROW SECURITY allows all kinds of things.  I have a more 
complicated sample we're still poking at, using a full pl/pgsql function 
there, but I've already got enough to try and cover in this message to 
bring that in today.  One reason the users I've been talking to feel 
comfortable that they'll be able to build apps usefully with this 
feature is this interface.  It feels more like a pluggable API for 
defining rules than a specific implementation. Congratulations to the 
usual community members who expect complete feature overkill in every 
design.

== Code review ==

Onto coding.  Rather than trying to cope with patches I pulled KaiGai's 
tree from https://github.com/kaigai/sepgsql/tree/rowsec to try things 
out.  That hasn't been merged against head in a while and it's suffered 
serious but not terminal bit rot.  The REPLICA IDENTITY changes in 
particular touched adjacent code all over the place, since it and RLS 
are both added things to the end of existing lists.  The merges to get 
thing back to working again were only hard when my eyes crossed on the 
always fun pg_class bootstrapping table.  I did all that myself up to 
committed changes on Dec 3.  The only real change I made was to punt the 
OID number assignment into a whole different range, to make future merge 
rot less likely.  Those are going to get reassigned at commit time 
anyway, no need to try and guess the appropriate next number for each today.

I just pushed a fork of KaiGai's repo to 
https://github.com/gregs1104/sepgsql/tree/rls that includes all of the 
must fix items to get the feature working again.  That's what the 
attached demo code was tested again.  The regression tests are still 
seriously busted, beyond just regenerating new output.  I pushed that 
off for now, I need to study recent commits better to catch up on what 
changed.  For community reference I'm attaching an updated patch that 
forks master after dfd5151c5800448a2be521797f95e1aa63d87b67 and then has 
the merging I did.  I'm hoping that getting a positive functional review 
will lure KaiGai back to help me update things.  Hopefully pulling or 
looking at commits from my merge attempt helps with that.

The (blue) elephant in the room here for this feature is the list of 
committers in particular who are very picky about the parts of the code 
this feature is touching (*cough* Tom).  One reason I'm trying to get 
these demo pieces together--Jeff here is building a whole RLS test 
sample repo--is to try and make the job of testing this out easier.  As 
I dig myself out of the hole I'm in after getting really sick instead of 
going to PG.EU, I've got a whole lot of time set aside to work on 
community features lined up.

== Outstanding issues ==

Things I can already see to work on here are:

-Fix the regression tests
-Catch up to master again
-Continue to expand the functional tests
-Is there enough information about row security available in psql 
output?  For example, there's nothing in "\d" output that suggests it 
might exist.  pg_rowsecurity is a monster to try and read.
-Documentation needs plenty of editing, which I can take care of.

== Cover Channels ==

One last topic since I know it's been a sore point.  Covert channels.  
There's still some obvious ones around.  In this example I was plenty 
uneasy about some of what you can learn from the EXPLAIN output too.  
But no one I've talked to about deploying PostgreSQL RLS seem terrible 
uncomfortable with the exposure at this point.  RLS is just one layer of 
extra protection on checks that are also happening in the application 
API exposing the data to users. No one expects any one layer to be the 
impenetrable one.  In the interest of CYA, I've talked through some 
scenarios where attackers have resources like a hacked libpq client and 
enough info to search the key space.  Instead of expecting that the 
database can survive that, which is pretty unreasonable, what I'm 
getting requests for now is better audit logging to make it easier to 
detect that.  We should certainly close any holes we can, but where 
things are at today doesn't seem functionally unacceptable anymore.

--
Greg Smith greg.smith@crunchydatasolutions.com
Chief PostgreSQL Evangelist - http://crunchydatasolutions.com/
-- Remove changes TO allow re-running the example
DROP TABLE IF EXISTS colors_shapes;
DROP ROLE alpha; 
DROP ROLE beta;
DROP ROLE gamma;
DROP ROLE blue_role;
DROP ROLE purple_role;
DROP ROLE triangle_role;
DROP ROLE heart_role;
DROP ROLE circle_role;
DROP ROLE square_role;

-- Create real user roles
CREATE ROLE alpha;
CREATE ROLE beta;
CREATE ROLE gamma;

-- Populate the sample table
CREATE table colors_shapes (id integer, color varchar(20), shape varchar(20));
INSERT INTO colors_shapes values (1,'blue_role','triangle_role');
INSERT INTO colors_shapes values (2, 'red_role', 'heart_role');
INSERT INTO colors_shapes values (3, 'purple_role', 'circle_role');

-- Create permissions granting roles and grant a subset of them to the users
CREATE ROLE blue_role;
CREATE ROLE red_role;
CREATE ROLE purple_role;
CREATE ROLE triangle_role;
CREATE ROLE heart_role;
CREATE ROLE circle_role;
CREATE ROLE square_role;

GRANT blue_role TO alpha;
GRANT purple_role TO alpha;
GRANT purple_role TO beta;
GRANT triangle_role TO alpha;
GRANT circle_role TO alpha;
GRANT square_role TO alpha;
GRANT red_role TO gamma;
GRANT heart_role TO gamma;

-- Only allow viewing a row if the user has a matching <color>_role and <shape>_role entry
ALTER TABLE colors_shapes
  SET ROW SECURITY FOR ALL TO (
  color IN (
    SELECT permission_role.rolname FROM pg_roles active_role, pg_auth_members, pg_roles permission_role WHERE
      active_role.rolname = current_user AND
      active_role.oid = pg_auth_members.member AND
      permission_role.oid = pg_auth_members.roleid
    )
  AND shape IN (
    SELECT permission_role.rolname FROM pg_roles active_role, pg_auth_members, pg_roles permission_role WHERE
      active_role.rolname = current_user AND
      active_role.oid = pg_auth_members.member AND
      permission_role.oid = pg_auth_members.roleid
    )
);
GRANT ALL ON colors_shapes TO alpha;
GRANT ALL ON colors_shapes TO beta;
GRANT ALL ON colors_shapes TO gamma;
SELECT relname,relhasrowsecurity FROM pg_class WHERE relhasrowsecurity;

-- Show the full working data set as superuser
SELECT * FROM colors_shapes;

-- Demonstrate the three views 
SET SESSION AUTHORIZATION alpha;
SELECT current_user, * FROM colors_shapes;
SET SESSION AUTHORIZATION beta;
SELECT current_user, * FROM colors_shapes;
SET SESSION AUTHORIZATION gamma;
SELECT current_user, * FROM colors_shapes;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index acc261c..631c5d2
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 234,239 ****
--- 234,244 ----
       </row>
  
       <row>
+       <entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecuritylevelsec</structname></link></entry>
+       <entry>row-level security policy of relation</entry>
+      </row>
+ 
+      <row>
        <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
        <entry>security labels on database objects</entry>
       </row>
***************
*** 1855,1860 ****
--- 1860,1875 ----
       </row>
  
       <row>
+       <entry><structfield>relhasrowsecurity</structfield></entry>
+       <entry><type>bool</type></entry>
+       <entry></entry>
+       <entry>
+        True if table has row-security policy; see
+        <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>relhassubclass</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
***************
*** 5135,5140 ****
--- 5150,5205 ----
  
   </sect1>
  
+  <sect1 id="catalog-pg-rowsecurity">
+   <title><structname>pg_rowsecurity</structname></title>
+ 
+   <indexterm zone="catalog-pg-rowsecurity">
+    <primary>pg_rowsecurity</primary>
+   </indexterm>
+   <para>
+    The catalog <structname>pg_rowsecurity</structname> stores expression
+    tree of row-security policy to be performed on a particular relation.
+   </para>
+   <table>
+    <title><structname>pg_rowsecurity</structname> Columns</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>References</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>rsecrelid</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+       <entry>The table this row-security is for</entry>
+      </row>
+      <row>
+       <entry><structfield>rseccmd</structfield></entry>
+       <entry><type>char</type></entry>
+       <entry></entry>
+       <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+      </row>
+      <row>
+       <entry><structfield>rsecqual</structfield></entry>
+       <entry><type>pg_node_tree</type></entry>
+       <entry></entry>
+       <entry>An expression tree to be performed as row-security policy</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+   <note>
+    <para>
+     <literal>pg_class.relhasrowsecurity</literal>
+     must be true if a table has row-security policy in this catalog.
+    </para>
+   </note>
+  </sect1>
  
   <sect1 id="catalog-pg-seclabel">
    <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
new file mode 100644
index 89649a2..79f37d8
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 69,74 ****
--- 69,76 ----
      NOT OF
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+     SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+     RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
      REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
  
  <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 76,81 ****
--- 78,85 ----
      [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
      { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
      [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ <phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+     { ALL | SELECT | INSERT | UPDATE | DELETE }
  </synopsis>
   </refsynopsisdiv>
  
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 581,586 ****
--- 585,610 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+     <listitem>
+      <para>
+       This form set row-level security policy of the table.
+       Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+       as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+       clause, although mechanism guarantees to evaluate this condition earlier
+       than any other user given condition.
+       <literal>ALL</> is the only supported command type right now.
+       See also <xref linkend="ROW-SECURITY">.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form reset row-level security policy of the table, if exists.
+       <literal>ALL</> is the only supported command type right now.
      <term><literal>REPLICA IDENTITY</literal></term>
      <listitem>
       <para>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 840,845 ****
--- 864,883 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">condition</replaceable></term>
+       <listitem>
+        <para>
+         An expression that returns a value of type boolean. Expect for a case
+         when queries are executed with superuser privilege, only rows for which
+         this expression returns true will be fetched, updated or deleted.
+         This expression can reference columns of the relation being configured.
+         Sub-queries can be contained within expression tree, unless referenced
+         relation recursively references the same relation.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
   </refsect1>
  
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
new file mode 100644
index 177ac7a..c764189
*** a/doc/src/sgml/user-manag.sgml
--- b/doc/src/sgml/user-manag.sgml
*************** DROP ROLE <replaceable>name</replaceable
*** 439,442 ****
--- 439,587 ----
    </para>
   </sect1>
  
+  <sect1 id="row-security">
+   <title>Row-security</title>
+   <para>
+    <productname>PostgreSQL</productname> v9.4 or later provides
+    row-security feature, like several commercial database
+    management system. It allows table owner to assign a particular
+    condition that performs as a security policy of the table; only
+    rows that satisfies the condition should be visible, except for
+    a case when superuser runs queries.
+   </para>
+   <para>
+    Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+    command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can contain
+    references to columns of the relation, so it enables to construct
+    arbitrary rule to make access control decision based on contents of
+    each rows.
+   </para>
+   <para>
+    For example, the following <literal>customer</literal> table has
+    <literal>uname</literal> field to store user name, and it assume
+    we don't want to expose any properties of other customers.
+    The following command set <literal>current_user = uname</literal>
+    as row-security policy on the <literal>customer</literal> table.
+ <screen>
+ postgres=> ALTER TABLE customer SET ROW SECURITY
+                                 FOR ALL TO (current_user = uname);
+ ALTER TABLE
+ </screen>
+    <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+    works on the supplied query.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                  QUERY PLAN
+ --------------------------------------------
+  Subquery Scan on customer
+    Filter: f_leak(customer.upasswd)
+    ->  Seq Scan on customer customer_1
+          Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+    This query execution plan means the preconfigured row-security policy is
+    implicitly added, and scan plan on the target relation being wrapped up
+    with a sub-query.
+    It ensures user given qualifiers, including functions with side effects,
+    are never executed earlier than the row-security policy regardless of
+    its cost, except for the cases when these were fully leakproof.
+    This design helps to tackle the scenario described in
+    <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+    qualifiers is significant to keep confidentiality of invisible rows.
+   </para>
+ 
+   <para>
+    On the other hand, this design allows superusers to bypass checks with
+    row-security.
+    It ensures <application>pg_dump</application> can obtain a complete set
+    of database backup, and avoid to execute Trojan horse trap, being injected
+    as a row-security policy of user-defined table, with privileges of
+    superuser.
+   </para>
+ 
+   <para>
+    In case of queries on inherited tables, row-security policy of the parent
+    relation is not applied to child relations. Scope of the row-security
+    policy is limited to the relation on which it is set.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+              QUERY PLAN
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.y)
+          ->  Seq Scan on t1 t1_1
+                Filter: ((x % 2) = 0)
+    ->  Seq Scan on t2
+          Filter: f_leak(y)
+    ->  Subquery Scan on t3
+          Filter: f_leak(t3.y)
+          ->  Seq Scan on t3 t3_1
+                Filter: ((x % 2) = 1)
+ (11 rows)
+ </screen>
+     In the above example, <literal>t1</literal> has inherited
+     child table <literal>t2</literal> and <literal>t3</literal>,
+     and row-security policy is set on only <literal>t1</literal>,
+     and <literal>t3</literal>, not <literal>t2</literal>.
+ 
+     The row-security policy of <literal>t1</literal>, <literal>x</literal>
+     must be even-number, is appended only <literal>t1</literal>,
+     neither <literal>t2</literal> nor <literal>t3</literal>.
+     On the contrary, <literal>t3</literal> has different row-security policy;
+     <literal>x</literal> must be odd-number.
+   </para>
+ 
+   <para>
+    Row-security feature also works to queries for writer-operations; such as
+    <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+    <xref linkend="SQL-DELETE"> commands.
+    It ensures all the modified rows satisfies configured row-security policy.
+    The below query tries to update e-mail address of the
+    <literal>customer</> table, and configured row-security makes sure all the
+    modified rows's <literal>uname</> field has to match with
+    <literal>current_user</>.
+   </para>
+ <screen>
+ postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                     QUERY PLAN
+ --------------------------------------------------
+  Update on customer
+    ->  Subquery Scan on customer
+          ->  Seq Scan on customer customer_1
+                Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+   <para>
+    Modification of a certain table is consist of two different stuffs; one
+    is fetch rows to be modified from the result relation (except for
+    <literal>INSERT</> command), second is insertion of rows to the
+    result relation. Usually, before-row trigger or check constraints are
+    run in the second phase. In addition, row-security policy is also checked
+    on this stage, to prevent to insert or update rows with unprivileged
+    values.
+   </para>
+ 
+   <para>
+    Unlike other commercial database systems, we don't have any plan to allow
+    individual row-security policy for each command type. Even if we want to
+    perform with difference policy between <xref linkend="SQL-SELECT"> and
+    <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+    to be invisible using <xref linkend="SQL-DELETE"> command.
+   </para>
+ 
+   <para>
+    Even though it is not a specific matter in row-security, please be careful
+    if you plan to use <literal>current_user</> in row-level security policy.
+    <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+    switch current user identifier during execution of the query.
+    Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+    then remaining rows with privilege of <literal>bob</>. If and when query
+    execution plan contains some kind of materialization and row-security
+    policy contains <literal>current_user</>, the fetched tuples in
+    <literal>bob</>'s screen might be evaluated according to the privilege of
+    <literal>alice</>.
+   </para>
+  </sect1>
  </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index a974bd5..beb73df
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** OBJS = catalog.o dependency.o heap.o ind
*** 15,21 ****
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
--- 15,21 ----
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_rowsecurity.o pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
*************** 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/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index 908126c..3590ec5
*** 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"
*************** doDeletion(const ObjectAddress *object, 
*** 1249,1254 ****
--- 1250,1259 ----
  			RemoveEventTriggerById(object->objectId);
  			break;
  
+ 		case OCLASS_ROWSECURITY:
+ 			RemoveRowSecurityById(object->objectId);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized object class: %u",
  				 object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2316,2321 ****
--- 2321,2329 ----
  
  		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 6f2e142..1ad6955
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** InsertPgClassTuple(Relation pg_class_des
*** 798,803 ****
--- 798,804 ----
  	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 9011190..e66c91b
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** getObjectDescription(const ObjectAddress
*** 2143,2148 ****
--- 2143,2198 ----
  				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, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index ...34d33e8
*** a/src/backend/catalog/pg_rowsecurity.c
--- b/src/backend/catalog/pg_rowsecurity.c
***************
*** 0 ****
--- 1,337 ----
+ /* -------------------------------------------------------------------------
+  *
+  * pg_rowsecurity.c
+  *    routines to support manipulation of the pg_rowsecurity catalog
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * -------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "access/genam.h"
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "parser/parse_relation.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/inval.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "utils/tqual.h"
+ 
+ /*
+  * Load row-security policy from the catalog, and keep it on
+  * 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();
+ 	{
+ 		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ 		{
+ 			Datum	value;
+ 			bool	isnull;
+ 			char   *temp;
+ 
+ 			if (!rsdesc)
+ 			{
+ 				rscxt = AllocSetContextCreate(CacheMemoryContext,
+ 											  "Row-security descriptor",
+ 											  ALLOCSET_SMALL_MINSIZE,
+ 											  ALLOCSET_SMALL_INITSIZE,
+ 											  ALLOCSET_SMALL_MAXSIZE);
+ 				oldcxt = MemoryContextSwitchTo(rscxt);
+ 				rsdesc = palloc0(sizeof(RowSecurityDesc));
+ 				rsdesc->rscxt = rscxt;
+ 				MemoryContextSwitchTo(oldcxt);
+ 			}
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 
+ 			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+ 			{
+ 				ereport(WARNING,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("Per-command row-security not implemented")));
+ 				continue;
+ 			}
+ 
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 			temp = TextDatumGetCString(value);
+ 
+ 			oldcxt = MemoryContextSwitchTo(rscxt);
+ 			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+ 			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+ 			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+ 			rsdesc->rsall.hassublinks
+ 				= contain_subplans((Node *)rsdesc->rsall.qual);
+ 			MemoryContextSwitchTo(oldcxt);
+ 
+ 			pfree(temp);
+ 		}
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (rscxt != NULL)
+ 			MemoryContextDelete(rscxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, AccessShareLock);
+ 
+ 	relation->rsdesc = rsdesc;
+ }
+ 
+ /*
+  * Parse the supplied row-security policy, and insert/update a row
+  * of pg_rowsecurity catalog.
+  */
+ static void
+ InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+ {
+ 	Oid				relationId = RelationGetRelid(relation);
+ 	Oid				rowsecId;
+ 	ParseState	   *pstate;
+ 	RangeTblEntry  *rte;
+ 	Node		   *qual;
+ 	Relation		catalog;
+ 	ScanKeyData		skeys[2];
+ 	SysScanDesc		sscan;
+ 	HeapTuple		oldtup;
+ 	HeapTuple		newtup;
+ 	Datum			values[Natts_pg_rowsecurity];
+ 	bool			isnull[Natts_pg_rowsecurity];
+ 	bool			replaces[Natts_pg_rowsecurity];
+ 	ObjectAddress	target;
+ 	ObjectAddress	myself;
+ 
+ 	/* Parse the supplied clause */
+ 	pstate = make_parsestate(NULL);
+ 
+ 	rte = addRangeTableEntryForRelation(pstate, relation,
+ 										NULL, false, false);
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	qual = transformWhereClause(pstate, copyObject(clause),
+ 								EXPR_KIND_ROW_SECURITY,
+ 								"ROW SECURITY");
+ 	/* zero-clear */
+ 	memset(values,   0, sizeof(values));
+ 	memset(replaces, 0, sizeof(replaces));
+ 	memset(isnull,   0, sizeof(isnull));
+ 
+ 	/* Update or Insert an entry to pg_rowsecurity catalog  */
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skeys[0],
+ 				Anum_pg_rowsecurity_rsecrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(RelationGetRelid(relation)));
+ 	ScanKeyInit(&skeys[1],
+ 				Anum_pg_rowsecurity_rseccmd,
+ 				BTEqualStrategyNumber, F_CHAREQ,
+ 				CharGetDatum(rseccmd));
+ 	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+ 							   NULL, 2, skeys);
+ 	oldtup = systable_getnext(sscan);
+ 	if (HeapTupleIsValid(oldtup))
+ 	{
+ 		rowsecId = HeapTupleGetOid(oldtup);
+ 
+ 		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 
+ 		newtup = heap_modify_tuple(oldtup,
+ 								   RelationGetDescr(catalog),
+ 								   values, isnull, replaces);
+ 		simple_heap_update(catalog, &newtup->t_self, newtup);
+ 
+ 		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+ 	}
+ 	else
+ 	{
+ 		values[Anum_pg_rowsecurity_rsecrelid - 1]
+ 			= ObjectIdGetDatum(relationId);
+ 		values[Anum_pg_rowsecurity_rseccmd - 1]
+ 			= CharGetDatum(rseccmd);
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 		newtup = heap_form_tuple(RelationGetDescr(catalog),
+ 								 values, isnull);
+ 		rowsecId = simple_heap_insert(catalog, newtup);
+ 	}
+ 	CatalogUpdateIndexes(catalog, newtup);
+ 
+ 	heap_freetuple(newtup);
+ 
+ 	/* records dependencies of row-security policy and relation/columns */
+ 	target.classId = RelationRelationId;
+ 	target.objectId = relationId;
+ 	target.objectSubId = 0;
+ 
+ 	myself.classId = RowSecurityRelationId;
+ 	myself.objectId = rowsecId;
+ 	myself.objectSubId = 0;
+ 
+ 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+ 
+ 	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ 						   DEPENDENCY_NORMAL);
+ 	free_parsestate(pstate);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * Remove row-security policy row of pg_rowsecurity
+  */
+ static void
+ DeletePolicyRow(Relation relation, char rseccmd)
+ {
+ 	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+ 
+ 	if (relation->rsdesc)
+ 	{
+ 		ObjectAddress	address;
+ 
+ 		address.classId = RowSecurityRelationId;
+ 		address.objectId = relation->rsdesc->rsall.rsecid;
+ 		address.objectSubId = 0;
+ 
+ 		performDeletion(&address, DROP_RESTRICT, 0);
+ 	}
+ 	else
+ 	{
+ 		/* Nothing to do here */
+ 		elog(INFO, "relation %s has no row-security policy, skipped",
+ 			 RelationGetRelationName(relation));
+ 	}
+ }
+ 
+ /*
+  * Guts of row-security policy deletion.
+  */
+ void
+ RemoveRowSecurityById(Oid rowsecId)
+ {
+ 	Relation	catalog;
+ 	ScanKeyData	skey;
+ 	SysScanDesc	sscan;
+ 	HeapTuple	tuple;
+ 	Relation	rel;
+ 	Oid			relid;
+ 
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skey,
+ 				ObjectIdAttributeNumber,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(rowsecId));
+ 	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+ 							   NULL, 1, &skey);
+ 	tuple = systable_getnext(sscan);
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+ 
+ 	/*
+ 	 * Open and exclusive-lock the relation the row-security belongs to.
+ 	 */
+ 	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+ 
+ 	rel = heap_open(relid, AccessExclusiveLock);
+ 
+ 	simple_heap_delete(catalog, &tuple->t_self);
+ 
+ 	/* Ensure relcache entries of other session being rebuilt */
+ 	CacheInvalidateRelcache(rel);
+ 
+ 	heap_close(rel, NoLock);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * ALTER TABLE <name> SET ROW SECURITY (...) OR
+  *                    RESET ROW SECURITY
+  */
+ void
+ ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+ {
+ 	Oid			relid = RelationGetRelid(relation);
+ 	char		rseccmd;
+ 
+ 	if (strcmp(cmdname, "all") == 0)
+ 		rseccmd = ROWSECURITY_CMD_ALL;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("Row-security for \"%s\" is not implemented yet",
+ 						cmdname)));
+ 
+ 	if (clause != NULL)
+ 	{
+ 		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+ 
+ 		/*
+ 		 * Also, turn on relhasrowsecurity, if not.
+ 		 */
+ 		if (!RelationGetForm(relation)->relhasrowsecurity)
+ 		{
+ 			Relation	class_rel = heap_open(RelationRelationId,
+ 											  RowExclusiveLock);
+ 			HeapTuple	tuple;
+ 
+ 			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ 			if (!HeapTupleIsValid(tuple))
+ 				elog(ERROR, "cache lookup failed for relation %u", relid);
+ 
+ 			((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);
+ 		}
+ 	}
+ 	else
+ 		DeletePolicyRow(relation, rseccmd);
+ 
+ 	CacheInvalidateRelcache(relation);
+ }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 0fa83a6..d657931
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 24,29 ****
--- 24,30 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/heap.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
  #include "commands/copy.h"
***************
*** 34,48 ****
--- 35,53 ----
  #include "libpq/pqformat.h"
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/planner.h"
+ #include "optimizer/rowsecurity.h"
  #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
  #include "rewrite/rewriteHandler.h"
  #include "storage/fd.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/memutils.h"
  #include "utils/portal.h"
  #include "utils/rel.h"
*************** DoCopy(const CopyStmt *stmt, const char 
*** 814,819 ****
--- 819,839 ----
  		rel = heap_openrv(stmt->relation,
  						  (is_from ? RowExclusiveLock : AccessShareLock));
  
+ 		tupDesc = RelationGetDescr(rel);
+ 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+ 
+ 		/*
+ 		 * We have to run regular query, if the target relation has
+  		 * row-level security policy
+ 		 */
+ 		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+ 		{
+ 			heap_close(rel, NoLock);	/* close with keeping lock */
+ 			relid = InvalidOid;
+ 			rel = NULL;
+ 		}
+ 		else
+ 		{
  		relid = RelationGetRelid(rel);
  
  		rte = makeNode(RangeTblEntry);
*************** DoCopy(const CopyStmt *stmt, const char 
*** 822,829 ****
  		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) -
--- 842,847 ----
*************** DoCopy(const CopyStmt *stmt, const char 
*** 835,840 ****
--- 853,859 ----
  				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
  		}
  		ExecCheckRTPerms(list_make1(rte), true);
+ 		}
  	}
  	else
  	{
*************** ProcessCopyOptions(CopyState cstate,
*** 1193,1198 ****
--- 1212,1264 ----
  }
  
  /*
+  * Adjust Query tree constructed with row-level security feature.
+  * If WITH OIDS option was supplied, it adds Var node to reference
+  * object-id system column.
+  */
+ static void
+ fixup_oid_of_rls_query(Query *query)
+ {
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	Var			   *subvar;
+ 	ListCell	   *cell;
+ 	Form_pg_attribute	attform
+ 		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+ 
+ 	subrte = rt_fetch((Index) 1, query->rtable);
+ 	Assert(subrte->rtekind == RTE_RELATION);
+ 
+ 	if (!SearchSysCacheExists2(ATTNUM,
+ 							   ObjectIdGetDatum(subrte->relid),
+ 							   Int16GetDatum(attform->attnum)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 				 errmsg("table \"%s\" does not have OIDs",
+ 						get_rel_name(subrte->relid))));
+ 
+ 	subvar = makeVar((Index) 1,
+ 					 attform->attnum,
+ 					 attform->atttypid,
+ 					 attform->atttypmod,
+ 					 attform->attcollation,
+ 					 0);
+ 	subtle = makeTargetEntry((Expr *) subvar,
+ 							 0,
+ 							 pstrdup(NameStr(attform->attname)),
+ 							 false);
+ 
+ 	query->targetList = list_concat(list_make1(subtle),
+ 									query->targetList);
+ 	/* adjust resno of TargetEntry */
+ 	foreach (cell, query->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 		subtle->resno++;
+ 	}
+ }
+ 
+ /*
   * Common setup routines used by BeginCopyFrom and BeginCopyTo.
   *
   * Iff <binary>, unload or reload in the binary format, as opposed to the
*************** BeginCopy(bool is_from,
*** 1264,1269 ****
--- 1330,1354 ----
  		Assert(!is_from);
  		cstate->rel = NULL;
  
+ 		/*
+ 		 * In case when regular COPY TO was replaced because of row-level
+ 		 * security, "raw_query" node have already analyzed / rewritten
+ 		 * query tree.
+ 		 */
+ 		if (IsA(raw_query, Query))
+ 		{
+ 			query = (Query *) raw_query;
+ 
+ 			Assert(query->querySource == QSRC_ROW_SECURITY);
+ 			if (cstate->oids)
+ 			{
+ 				fixup_oid_of_rls_query(query);
+ 				cstate->oids = false;
+ 			}
+ 			attnamelist = NIL;
+ 		}
+ 		else
+ 		{
  		/* Don't allow COPY w/ OIDs from a select */
  		if (cstate->oids)
  			ereport(ERROR,
*************** BeginCopy(bool is_from,
*** 1288,1293 ****
--- 1373,1379 ----
  			elog(ERROR, "unexpected rewrite result");
  
  		query = (Query *) linitial(rewritten);
+ 		}
  
  		/* The grammar allows SELECT INTO, but we don't support that */
  		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 328e2a8..0374629
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 992,997 ****
--- 992,998 ----
  		case OCLASS_USER_MAPPING:
  		case OCLASS_DEFACL:
  		case OCLASS_EXTENSION:
+ 		case OCLASS_ROWSECURITY:
  			return true;
  
  		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index 1d9f29a..afe77c7
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 37,42 ****
--- 37,43 ----
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_opclass.h"
+ #include "catalog/pg_rowsecurity.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
*************** AlterTableGetLockLevel(List *cmds)
*** 2789,2794 ****
--- 2790,2797 ----
  			case AT_SetTableSpace:		/* must rewrite heap */
  			case AT_DropNotNull:		/* may change some SQL plans */
  			case AT_SetNotNull:
+ 			case AT_SetRowSecurity:
+ 			case AT_ResetRowSecurity:
  			case AT_GenericOptions:
  			case AT_AlterColumnGenericOptions:
  				cmd_lockmode = AccessExclusiveLock;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 3164,3169 ****
--- 3167,3174 ----
  		case AT_DropInherit:	/* NO INHERIT */
  		case AT_AddOf:			/* OF */
  		case AT_DropOf: /* NOT OF */
+ 		case AT_SetRowSecurity:
+ 		case AT_ResetRowSecurity:
  			ATSimplePermissions(rel, ATT_TABLE);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 3449,3454 ****
--- 3454,3464 ----
  		case AT_DropOf:
  			ATExecDropOf(rel, lockmode);
  			break;
+ 		case AT_SetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+ 			break;
+ 		case AT_ResetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, NULL);
  		case AT_ReplicaIdentity:
  			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
  			break;
*************** ATExecAlterColumnType(AlteredTableInfo *
*** 7808,7813 ****
--- 7818,7839 ----
  				Assert(defaultexpr);
  				break;
  
+ 			case OCLASS_ROWSECURITY:
+ 				/*
+ 				 * A row-level security policy can depend on a column in case
+ 				 * when the policy clause references a particular column.
+ 				 * Due to same reason why TRIGGER ... WHEN does not support
+ 				 * to change column's type being referenced in clause, row-
+ 				 * level security policy also does not support it.
+ 				 */
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("cannot alter type of a column used in a row-level security policy"),
+ 						 errdetail("%s depends on column \"%s\"",
+ 								   getObjectDescription(&foundObject),
+ 								   colName)));
+ 				break;
+ 
  			case OCLASS_PROC:
  			case OCLASS_TYPE:
  			case OCLASS_CAST:
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index 6be17a9..92772bc
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** InitPlan(QueryDesc *queryDesc, int eflag
*** 789,796 ****
  	foreach(l, plannedstmt->rowMarks)
  	{
  		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
! 		Oid			relid;
! 		Relation	relation;
  		ExecRowMark *erm;
  
  		/* ignore "parent" rowmarks; they are irrelevant at runtime */
--- 789,797 ----
  	foreach(l, plannedstmt->rowMarks)
  	{
  		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
! 		RangeTblEntry *rte = NULL;
! 		Relation	relation = NULL;
! 		LOCKMODE	lockmode = NoLock;
  		ExecRowMark *erm;
  
  		/* ignore "parent" rowmarks; they are irrelevant at runtime */
*************** InitPlan(QueryDesc *queryDesc, int eflag
*** 803,829 ****
  			case ROW_MARK_NOKEYEXCLUSIVE:
  			case ROW_MARK_SHARE:
  			case ROW_MARK_KEYSHARE:
! 				relid = getrelid(rc->rti, rangeTable);
! 				relation = heap_open(relid, RowShareLock);
  				break;
  			case ROW_MARK_REFERENCE:
! 				relid = getrelid(rc->rti, rangeTable);
! 				relation = heap_open(relid, AccessShareLock);
  				break;
  			case ROW_MARK_COPY:
  				/* there's no real table here ... */
- 				relation = NULL;
  				break;
  			default:
  				elog(ERROR, "unrecognized markType: %d", rc->markType);
- 				relation = NULL;	/* keep compiler quiet */
  				break;
  		}
  
  		/* Check that relation is a legal target for marking */
! 		if (relation)
  			CheckValidRowMarkRel(relation, rc->markType);
! 
  		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
  		erm->relation = relation;
  		erm->rti = rc->rti;
--- 804,836 ----
  			case ROW_MARK_NOKEYEXCLUSIVE:
  			case ROW_MARK_SHARE:
  			case ROW_MARK_KEYSHARE:
! 				rte = rt_fetch(rc->rti, rangeTable);
! 				lockmode = RowShareLock;
  				break;
  			case ROW_MARK_REFERENCE:
! 				rte = rt_fetch(rc->rti, rangeTable);
! 				lockmode = AccessShareLock;
  				break;
  			case ROW_MARK_COPY:
  				/* there's no real table here ... */
  				break;
  			default:
  				elog(ERROR, "unrecognized markType: %d", rc->markType);
  				break;
  		}
  
  		/* Check that relation is a legal target for marking */
! 		if (rte)
! 		{
! 			if (rte->rtekind == RTE_RELATION)
! 				relation = heap_open(rte->relid, lockmode);
! 			else
! 			{
! 				Assert(rte->rtekind == RTE_SUBQUERY);
! 				relation = heap_open(rte->rowsec_relid, lockmode);
! 			}
  			CheckValidRowMarkRel(relation, rc->markType);
! 		}
  		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
  		erm->relation = relation;
  		erm->rti = rc->rti;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index e3edcf6..d5ce3d2
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAppendRelInfo(const AppendRelInfo *
*** 1934,1939 ****
--- 1934,1940 ----
  
  	COPY_SCALAR_FIELD(parent_relid);
  	COPY_SCALAR_FIELD(child_relid);
+ 	COPY_SCALAR_FIELD(child_result);
  	COPY_SCALAR_FIELD(parent_reltype);
  	COPY_SCALAR_FIELD(child_reltype);
  	COPY_NODE_FIELD(translated_vars);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1975,1980 ****
--- 1976,1982 ----
  	COPY_SCALAR_FIELD(relkind);
  	COPY_NODE_FIELD(subquery);
  	COPY_SCALAR_FIELD(security_barrier);
+ 	COPY_SCALAR_FIELD(rowsec_relid);
  	COPY_SCALAR_FIELD(jointype);
  	COPY_NODE_FIELD(joinaliasvars);
  	COPY_NODE_FIELD(functions);
*************** _copyQuery(const Query *from)
*** 2463,2468 ****
--- 2465,2471 ----
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_SCALAR_FIELD(resultRelation);
+ 	COPY_SCALAR_FIELD(sourceRelation);
  	COPY_SCALAR_FIELD(hasAggs);
  	COPY_SCALAR_FIELD(hasWindowFuncs);
  	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 1f9b5d7..082d3d4
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAppendRelInfo(const AppendRelInfo 
*** 812,817 ****
--- 812,818 ----
  {
  	COMPARE_SCALAR_FIELD(parent_relid);
  	COMPARE_SCALAR_FIELD(child_relid);
+ 	COMPARE_SCALAR_FIELD(child_result);
  	COMPARE_SCALAR_FIELD(parent_reltype);
  	COMPARE_SCALAR_FIELD(child_reltype);
  	COMPARE_NODE_FIELD(translated_vars);
*************** _equalQuery(const Query *a, const Query 
*** 847,852 ****
--- 848,854 ----
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
  	COMPARE_SCALAR_FIELD(resultRelation);
+ 	COMPARE_SCALAR_FIELD(sourceRelation);
  	COMPARE_SCALAR_FIELD(hasAggs);
  	COMPARE_SCALAR_FIELD(hasWindowFuncs);
  	COMPARE_SCALAR_FIELD(hasSubLinks);
*************** _equalRangeTblEntry(const RangeTblEntry 
*** 2245,2250 ****
--- 2247,2253 ----
  	COMPARE_SCALAR_FIELD(relkind);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_SCALAR_FIELD(security_barrier);
+ 	COMPARE_SCALAR_FIELD(rowsec_relid);
  	COMPARE_SCALAR_FIELD(jointype);
  	COMPARE_NODE_FIELD(joinaliasvars);
  	COMPARE_NODE_FIELD(functions);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index d7db67d..b9b5aa2
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** query_tree_walker(Query *query,
*** 1941,1948 ****
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (walker((Node *) query->returningList, context))
! 		return true;
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
--- 1941,1951 ----
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 	{
! 		if (walker((Node *) query->returningList, context))
! 			return true;
! 	}
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
*************** query_tree_mutator(Query *query,
*** 2678,2684 ****
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	MUTATE(query->returningList, query->returningList, List *);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
--- 2681,2690 ----
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 		MUTATE(query->returningList, query->returningList, List *);
! 	else
! 		query->returningList = copyObject(query->returningList);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 4c7505e..fde3853
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAppendRelInfo(StringInfo str, const 
*** 1923,1928 ****
--- 1923,1929 ----
  
  	WRITE_UINT_FIELD(parent_relid);
  	WRITE_UINT_FIELD(child_relid);
+ 	WRITE_UINT_FIELD(child_result);
  	WRITE_OID_FIELD(parent_reltype);
  	WRITE_OID_FIELD(child_reltype);
  	WRITE_NODE_FIELD(translated_vars);
*************** _outQuery(StringInfo str, const Query *n
*** 2238,2243 ****
--- 2239,2245 ----
  		appendStringInfoString(str, " :utilityStmt <>");
  
  	WRITE_INT_FIELD(resultRelation);
+ 	WRITE_INT_FIELD(sourceRelation);
  	WRITE_BOOL_FIELD(hasAggs);
  	WRITE_BOOL_FIELD(hasWindowFuncs);
  	WRITE_BOOL_FIELD(hasSubLinks);
*************** _outRangeTblEntry(StringInfo str, const 
*** 2373,2378 ****
--- 2375,2381 ----
  		case RTE_SUBQUERY:
  			WRITE_NODE_FIELD(subquery);
  			WRITE_BOOL_FIELD(security_barrier);
+ 			WRITE_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 2e2cfa7..9f32519
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 199,204 ****
--- 199,205 ----
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
  	READ_INT_FIELD(resultRelation);
+ 	READ_INT_FIELD(sourceRelation);
  	READ_BOOL_FIELD(hasAggs);
  	READ_BOOL_FIELD(hasWindowFuncs);
  	READ_BOOL_FIELD(hasSubLinks);
*************** _readRangeTblEntry(void)
*** 1214,1219 ****
--- 1215,1221 ----
  		case RTE_SUBQUERY:
  			READ_NODE_FIELD(subquery);
  			READ_BOOL_FIELD(security_barrier);
+ 			READ_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 6670794..8b171ce
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
  #include "parser/analyze.h"
*************** standard_planner(Query *parse, int curso
*** 177,182 ****
--- 178,184 ----
  	glob->lastPHId = 0;
  	glob->lastRowMarkId = 0;
  	glob->transientPlan = false;
+ 	glob->planUserId = InvalidOid;
  
  	/* Determine what fraction of the plan is likely to be scanned */
  	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
*************** standard_planner(Query *parse, int curso
*** 254,259 ****
--- 256,262 ----
  	result->relationOids = glob->relationOids;
  	result->invalItems = glob->invalItems;
  	result->nParamExec = glob->nParamExec;
+ 	result->planUserId = glob->planUserId;
  
  	return result;
  }
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 404,409 ****
--- 407,425 ----
  	expand_inherited_tables(root);
  
  	/*
+ 	 * Apply row-security policy of the relation being referenced,
+ 	 * if configured with either of built-in or extension's features.
+ 	 * RangeTblEntry of the relation with row-security policy shall
+ 	 * be replaced with a row-security subquery that has simple scan
+ 	 * on the target relation with row-security policy qualifiers.
+ 	 *
+ 	 * This routine assumes PlannerInfo is already handled with
+ 	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+ 	 * have valid information.
+ 	 */
+ 	apply_row_security_policy(root);
+ 
+ 	/*
  	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
  	 * because preprocess_expression will reduce a constant-true condition to
  	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
*************** inheritance_planner(PlannerInfo *root)
*** 888,893 ****
--- 904,911 ----
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
*************** inheritance_planner(PlannerInfo *root)
*** 951,957 ****
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
--- 969,978 ----
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations,
! 									  (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid));
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index fb67f9e..0cccdf5
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 37,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
   * preprocess_targetlist
--- 37,85 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 							   Index result_relation, Index source_relation,
! 							   List *range_table);
! 
! /*
!  * lookup_varattno
!  *
!  * This routine returns an attribute number to reference a particular
!  * attribute. In case when the target relation is really relation,
!  * we can reference arbitrary attribute (including system column)
!  * without any translations. However, we have to translate varattno
!  * of Var that references sub-queries being originated from regular
!  * relations with row-level security policy due to nature of sub-query
!  * that has no system-column.
!  */
! static AttrNumber
! lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
! {
! 	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
! 
! 	if (rte->rtekind == RTE_SUBQUERY &&
! 		rte->subquery->querySource == QSRC_ROW_SECURITY)
! 	{
! 		ListCell   *cell;
! 
! 		foreach (cell, rte->subquery->targetList)
! 		{
! 			TargetEntry *tle = lfirst(cell);
! 			Var			*var;
! 
! 			if (IsA(tle->expr, Const))
! 				continue;
! 
! 			var = (Var *) tle->expr;
! 			Assert(IsA(var, Var));
  
+ 			if (var->varattno == attno)
+ 				return tle->resno;
+ 		}
+ 		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+ 			 attno);
+ 	}
+ 	return attno;
+ }
  
  /*
   * preprocess_targetlist
*************** preprocess_targetlist(PlannerInfo *root,
*** 51,56 ****
--- 92,98 ----
  {
  	Query	   *parse = root->parse;
  	int			result_relation = parse->resultRelation;
+ 	int			source_relation = parse->sourceRelation;
  	List	   *range_table = parse->rtable;
  	CmdType		command_type = parse->commandType;
  	ListCell   *lc;
*************** preprocess_targetlist(PlannerInfo *root,
*** 73,80 ****
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
--- 115,126 ----
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 	{
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation,
! 								  source_relation,
! 								  range_table);
! 	}
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
*************** preprocess_targetlist(PlannerInfo *root,
*** 96,102 ****
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  SelfItemPointerAttributeNumber,
  						  TIDOID,
  						  -1,
  						  InvalidOid,
--- 142,149 ----
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  lookup_varattno(SelfItemPointerAttributeNumber,
! 										  rc->rti, range_table),
  						  TIDOID,
  						  -1,
  						  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 112,118 ****
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  TableOidAttributeNumber,
  							  OIDOID,
  							  -1,
  							  InvalidOid,
--- 159,166 ----
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  lookup_varattno(TableOidAttributeNumber,
! 											  rc->rti, range_table),
  							  OIDOID,
  							  -1,
  							  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 195,201 ****
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
--- 243,250 ----
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Index source_relation,
! 				  List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
*************** expand_targetlist(List *tlist, int comma
*** 218,223 ****
--- 267,275 ----
  
  	numattrs = RelationGetNumberOfAttributes(rel);
  
+ 	if (source_relation == 0)
+ 		source_relation = result_relation;
+ 
  	for (attrno = 1; attrno <= numattrs; attrno++)
  	{
  		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
*************** expand_targetlist(List *tlist, int comma
*** 298,305 ****
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(result_relation,
! 													attrno,
  													atttype,
  													atttypmod,
  													attcollation,
--- 350,359 ----
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(source_relation,
! 													lookup_varattno(attrno,
! 														source_relation,
! 														range_table),
  													atttype,
  													atttypmod,
  													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index e249628..017bc2c
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** typedef struct
*** 55,60 ****
--- 55,61 ----
  {
  	PlannerInfo *root;
  	AppendRelInfo *appinfo;
+ 	bool		in_returning;
  } adjust_appendrel_attrs_context;
  
  static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1594,1599 ****
--- 1595,1601 ----
  
  	context.root = root;
  	context.appinfo = appinfo;
+ 	context.in_returning = false;
  
  	/*
  	 * Must be prepared to start with a Query or a bare expression tree.
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1605,1614 ****
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
--- 1607,1635 ----
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES |
! 									 QTW_IGNORE_RETURNING);
! 		/*
! 		 * Returning clause on the relation being replaced with row-
! 		 * security subquery shall be handled in a special way, because
! 		 * of no system columns on subquery.
! 		 * Var references to system column or whole-row reference need
! 		 * to be adjusted to reference pseudo columns on behalf of
! 		 * the underlying these columns, however, RETURNGIN clause is
! 		 * an exception because its Var nodes are evaluated towards
! 		 * the "raw" target relation, not a fetched tuple.
! 		 */
! 		context.in_returning = true;
! 		newnode->returningList = (List *)
! 			expression_tree_mutator((Node *) newnode->returningList,
! 									adjust_appendrel_attrs_mutator,
! 									(void *) &context);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid);
! 			newnode->sourceRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1624,1629 ****
--- 1645,1693 ----
  }
  
  static Node *
+ fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+ {
+ 	ListCell   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 	/*
+ 	 * In case when row-level security policy is applied on the referenced
+ 	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+ 	 * to filter out unprivileged rows of underlying relation.
+ 	 * Even though reference to this sub-query should perform as if ones
+ 	 * to real relations, system column has to be cared in special way
+ 	 * due to the nature of sub-query.
+ 	 * Target-entries that reference system columns should be added on
+ 	 * rowlevelsec.c, so all we need to do here is looking up underlying
+ 	 * target-list that can reference underlying system column, and fix-
+ 	 * up varattno of the referencing Var node with resno of TargetEntry.
+ 	 */
+ 	foreach (cell, rte->subquery->targetList)
+ 	{
+ 		TargetEntry *subtle = lfirst(cell);
+ 
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *) subtle->expr;
+ 			Var	   *newnode;
+ 
+ 			if (subvar->varattno == var->varattno)
+ 			{
+ 				newnode = copyObject(var);
+ 				newnode->varattno = subtle->resno;
+ 				return (Node *)newnode;
+ 			}
+ 		}
+ 		else
+ 			Assert(IsA(subtle->expr, Const));
+ 	}
+ 	elog(ERROR, "could not find pseudo column of %d, relid %u",
+ 		 var->varattno, var->varno);
+ 	return NULL;
+ }
+ 
+ static Node *
  adjust_appendrel_attrs_mutator(Node *node,
  							   adjust_appendrel_attrs_context *context)
  {
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1638,1645 ****
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = appinfo->child_relid;
  			var->varnoold = appinfo->child_relid;
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
--- 1702,1713 ----
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = (context->in_returning &&
! 						  appinfo->child_result > 0 ?
! 						  appinfo->child_result :
! 						  appinfo->child_relid);
  			var->varnoold = appinfo->child_relid;
+ 
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1664,1669 ****
--- 1732,1745 ----
  				 */
  				if (OidIsValid(appinfo->child_reltype))
  				{
+ 					Query          *parse = context->root->parse;
+ 					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+ 												   parse->rtable);
+ 					if (!context->in_returning &&
+ 						rte->rtekind == RTE_SUBQUERY &&
+ 						rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+ 
  					Assert(var->vartype == appinfo->parent_reltype);
  					if (appinfo->parent_reltype != appinfo->child_reltype)
  					{
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1708,1714 ****
  					return (Node *) rowexpr;
  				}
  			}
! 			/* system attributes don't need any other translation */
  		}
  		return (Node *) var;
  	}
--- 1784,1801 ----
  					return (Node *) rowexpr;
  				}
  			}
! 			else
! 			{
! 				Query		   *parse = context->root->parse;
! 				RangeTblEntry  *rte;
! 
! 				rte = rt_fetch(appinfo->child_relid, parse->rtable);
! 
! 				if (!context->in_returning &&
! 					rte->rtekind == RTE_SUBQUERY &&
! 					rte->subquery->querySource == QSRC_ROW_SECURITY)
! 					return fixup_var_on_rowsec_subquery(rte, var);
! 			}
  		}
  		return (Node *) var;
  	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
new file mode 100644
index 3b2d16b..3f5cb19
*** a/src/backend/optimizer/util/Makefile
--- b/src/backend/optimizer/util/Makefile
*************** top_builddir = ../../../..
*** 13,18 ****
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 13,18 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index ...c2e5a49
*** a/src/backend/optimizer/util/rowsecurity.c
--- b/src/backend/optimizer/util/rowsecurity.c
***************
*** 0 ****
--- 1,744 ----
+ /*
+  * optimizer/util/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/plannodes.h"
+ #include "optimizer/clauses.h"
+ #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
+ #include "parser/parsetree.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+ 
+ /* flags to pull row-security policy */
+ #define RSEC_FLAG_HAS_SUBLINKS			0x0001
+ 
+ /* hook to allow extensions to apply their own security policy */
+ row_security_policy_hook_type	row_security_policy_hook = NULL;
+ 
+ /*
+  * make_pseudo_column
+  *
+  * It makes a target-entry node that references underlying column.
+  * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+  * if the supplied attribute was already dropped.
+  */
+ static TargetEntry *
+ make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+ {
+ 	Expr   *expr;
+ 	char   *resname;
+ 
+ 	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+ 	if (attnum == InvalidAttrNumber)
+ 	{
+ 		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+ 		resname = get_rel_name(subrte->relid);
+ 	}
+ 	else
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(subrte->relid),
+ 								Int16GetDatum(attnum));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ 				 attnum, subrte->relid);
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		if (attform->attisdropped)
+ 		{
+ 			char	namebuf[NAMEDATALEN];
+ 
+ 			/* Insert NULL just for a placeholder of dropped column */
+ 			expr = (Expr *) makeConst(INT4OID,
+ 									  -1,
+ 									  InvalidOid,
+ 									  sizeof(int32),
+ 									  (Datum) 0,
+ 									  true,		/* isnull */
+ 									  true);	/* byval */
+ 			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+ 			resname = pstrdup(namebuf);
+ 		}
+ 		else
+ 		{
+ 			expr = (Expr *) makeVar((Index) 1,
+ 									attform->attnum,
+ 									attform->atttypid,
+ 									attform->atttypmod,
+ 									attform->attcollation,
+ 									0);
+ 			resname = pstrdup(NameStr(attform->attname));
+ 		}
+ 		ReleaseSysCache(tuple);
+ 	}
+ 	return makeTargetEntry(expr, -1, resname, false);
+ }
+ 
+ /*
+  * lookup_pseudo_column
+  *
+  * It looks-up resource number of the target-entry relevant to the given
+  * Var-node that references the row-security subquery. If required column
+  * is not in the subquery's target-list, this function also adds new one
+  * and returns its resource number.
+  */
+ static AttrNumber
+ lookup_pseudo_column(PlannerInfo *root,
+ 					 RangeTblEntry *rte, AttrNumber varattno)
+ {
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	ListCell	   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 	subqry = rte->subquery;
+ 	foreach (cell, subqry->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 
+ 		/*
+ 		 * If referenced artifical column is already constructed on the
+ 		 * target-list of row-security subquery, nothing to do any more.
+ 		 */
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *)subtle->expr;
+ 
+ 			Assert(subvar->varno == 1);
+ 			if (subvar->varattno == varattno)
+ 				return subtle->resno;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * OK, we don't have an artifical column relevant to the required ones,
+ 	 * so let's create a new artifical column on demand.
+ 	 */
+ 	subrte = rt_fetch((Index) 1, subqry->rtable);
+ 	subtle = make_pseudo_column(subrte, varattno);
+ 	subtle->resno = list_length(subqry->targetList) + 1;
+ 
+ 	subqry->targetList = lappend(subqry->targetList, subtle);
+ 	rte->eref->colnames = lappend(rte->eref->colnames,
+ 								  makeString(pstrdup(subtle->resname)));
+ 	return subtle->resno;
+ }
+ 
+ /*
+  * fixup_varnode_walker
+  *
+  * It recursively fixes up references to the relation to be replaced by
+  * row-security sub-query, and adds pseudo columns relevant to the
+  * underlying system columns or whole row-reference on demand.
+  */
+ typedef struct {
+ 	PlannerInfo	*root;
+ 	int		varlevelsup;
+ 	Index  *vartrans;
+ } fixup_varnode_context;
+ 
+ static bool
+ fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	if (IsA(node, Var))
+ 	{
+ 		Var			   *var = (Var *) node;
+ 		List		   *rtable = context->root->parse->rtable;
+ 		RangeTblEntry  *rte;
+ 		ListCell	   *cell;
+ 
+ 		/*
+ 		 * Ignore it, if Var node does not reference the Query currently
+ 		 * we focus on.
+ 		 */
+ 		if (var->varlevelsup != context->varlevelsup)
+ 			return false;
+ 
+ 		if (context->vartrans[var->varno] > 0)
+ 		{
+ 			Index	rtindex_trans = context->vartrans[var->varno];
+ 
+ 			rte = rt_fetch(rtindex_trans, rtable);
+ 			Assert(rte->rtekind == RTE_SUBQUERY &&
+ 				   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 			var->varno = var->varnoold = rtindex_trans;
+ 			var->varattno = lookup_pseudo_column(context->root, rte,
+ 													 var->varattno);
+ 		}
+ 		else
+ 		{
+ 			rte = rt_fetch(var->varno, rtable);
+ 			if (rte->rtekind == RTE_RELATION && rte->inh)
+ 			{
+ 				foreach (cell, context->root->append_rel_list)
+ 				{
+ 					AppendRelInfo  *appinfo = lfirst(cell);
+ 					RangeTblEntry  *child_rte;
+ 
+ 					if (appinfo->parent_relid != var->varno)
+ 						continue;
+ 
+ 					child_rte = rt_fetch(appinfo->child_relid, rtable);
+ 					if (child_rte->rtekind == RTE_SUBQUERY &&
+ 						child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						(void) lookup_pseudo_column(context->root,
+ 													child_rte,
+ 													var->varattno);
+ 				}
+ 			}
+ 		}
+ 	}
+ 	else if (IsA(node, RangeTblRef))
+ 	{
+ 		RangeTblRef  *rtr = (RangeTblRef *) node;
+ 
+ 		if (context->varlevelsup == 0 &&
+ 			context->vartrans[rtr->rtindex] != 0)
+ 			rtr->rtindex = context->vartrans[rtr->rtindex];
+ 	}
+ 	else if (IsA(node, Query))
+ 	{
+ 		bool	result;
+ 
+ 		context->varlevelsup++;
+ 		result = query_tree_walker((Query *) node,
+ 								   fixup_varnode_walker,
+ 								   (void *) context, 0);
+ 		context->varlevelsup--;
+ 
+ 		return result;
+ 	}
+ 	return expression_tree_walker(node,
+ 								  fixup_varnode_walker,
+ 								  (void *) context);
+ }
+ 
+ /*
+  * check_infinite_recursion
+  *
+  * It is a wrong row-security configuration, if we try to expand
+  * the relation inside of row-security subquery originated from
+  * same relation!
+  */
+ static void
+ check_infinite_recursion(PlannerInfo *root, Oid relid)
+ {
+ 	PlannerInfo	   *parent = root->parent_root;
+ 
+ 	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+ 
+ 		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+ 
+ 		if (relid == rte->relid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("infinite recursion detected for relation \"%s\"",
+ 							get_rel_name(relid))));
+ 		check_infinite_recursion(parent, relid);
+ 	}
+ }
+ 
+ /*
+  * expand_rtentry_with_policy
+  *
+  * It extends a range-table entry of row-security sub-query with supplied
+  * security policy, and append it on the parse->rtable.
+  * This sub-query contains pseudo columns that reference underlying
+  * regular columns (at least, references to system column or whole of
+  * table reference shall be added on demand), and simple scan on the
+  * target relation.
+  * Any Var nodes that referenced the relation pointed by rtindex shall
+  * be adjusted to reference this sub-query instead. walker
+  */
+ static Index
+ expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+ 						   Expr *qual, int flags)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	RangeTblRef	   *subrtr;
+ 	TargetEntry	   *subtle;
+ 	RangeTblEntry  *newrte;
+ 	HeapTuple		tuple;
+ 	AttrNumber		nattrs;
+ 	AttrNumber		attnum;
+ 	List		   *targetList = NIL;
+ 	List		   *colNameList = NIL;
+ 	PlanRowMark	   *rowmark;
+ 
+ 	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+ 
+ 	/* check recursion to prevent infinite loop */
+ 	check_infinite_recursion(root, rte->relid);
+ 
+ 	/* Expand views inside SubLink node */
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+ 
+ 	/*
+ 	 * Construction of sub-query
+ 	 */
+ 	subqry = (Query *) makeNode(Query);
+ 	subqry->commandType = CMD_SELECT;
+ 	subqry->querySource = QSRC_ROW_SECURITY;
+ 
+ 	subrte = copyObject(rte);
+ 	subqry->rtable = list_make1(subrte);
+ 
+ 	subrtr = makeNode(RangeTblRef);
+ 	subrtr->rtindex = 1;
+ 	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		subqry->hasSubLinks = true;
+ 
+ 	/*
+ 	 * Construction of TargetEntries that reference underlying columns.
+ 	 */
+ 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+ 	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+ 	ReleaseSysCache(tuple);
+ 
+ 	for (attnum = 1; attnum <= nattrs; attnum++)
+ 	{
+ 		subtle = make_pseudo_column(subrte, attnum);
+ 		subtle->resno = list_length(targetList) + 1;
+ 		Assert(subtle->resno == attnum);
+ 
+ 		targetList = lappend(targetList, subtle);
+ 		colNameList = lappend(colNameList,
+ 							  makeString(pstrdup(subtle->resname)));
+ 	}
+ 	subqry->targetList = targetList;
+ 
+ 	/* Expand RengeTblEntry with this sub-query */
+ 	newrte = makeNode(RangeTblEntry);
+ 	newrte->rtekind = RTE_SUBQUERY;
+ 	newrte->subquery = subqry;
+ 	newrte->security_barrier = true;
+ 	newrte->rowsec_relid = rte->relid;
+ 	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+ 
+ 	parse->rtable = lappend(parse->rtable, newrte);
+ 
+ 	/*
+ 	 * Fix up PlanRowMark if needed, then add references to 'tableoid' and
+ 	 * 'ctid' that shall be added to handle row-level locking.
+ 	 * Also see preprocess_targetlist() that adds some junk attributes.
+ 	 */
+ 	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+ 	if (rowmark)
+ 	{
+ 		if (rowmark->rti == rowmark->prti)
+ 			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+ 		else
+ 			rowmark->rti = list_length(parse->rtable);
+ 
+ 		lookup_pseudo_column(root, newrte, SelfItemPointerAttributeNumber);
+ 		lookup_pseudo_column(root, newrte, TableOidAttributeNumber);
+ 	}
+ 	return list_length(parse->rtable);
+ }
+ 
+ /*
+  * pull_row_security_policy
+  *
+  * It pulls the configured row-security policy of both built-in and
+  * extensions. If any, it returns expression tree.
+  */
+ static Expr *
+ pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+ {
+ 	Expr   *quals = NULL;
+ 	int		flags = 0;
+ 
+ 	/*
+ 	 * Pull the row-security policy configured with built-in features,
+ 	 * if unprivileged users. Please note that superuser can bypass it.
+ 	 */
+ 	if (relation->rsdesc && !superuser())
+ 	{
+ 		RowSecurityDesc *rsdesc = relation->rsdesc;
+ 
+ 		quals = copyObject(rsdesc->rsall.qual);
+ 		if (rsdesc->rsall.hassublinks)
+ 			flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 	}
+ 
+ 	/*
+ 	 * Also, ask extensions whether they want to apply their own
+ 	 * row-security policy. If both built-in and extension has
+ 	 * their own policy, it shall be merged.
+ 	 */
+ 	if (row_security_policy_hook)
+ 	{
+ 		List   *temp;
+ 
+ 		temp = (*row_security_policy_hook)(cmd, relation);
+ 		if (temp != NIL)
+ 		{
+ 			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+ 				contain_subplans((Node *) temp))
+ 				flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 
+ 			if (quals != NULL)
+ 				temp = lappend(temp, quals);
+ 
+ 			if (list_length(temp) == 1)
+ 				quals = (Expr *)list_head(temp);
+ 			else if (list_length(temp) > 1)
+ 				quals = makeBoolExpr(AND_EXPR, temp, -1);
+ 		}
+ 	}
+ 	*p_flags = flags;
+ 	return quals;
+ }
+ 
+ /*
+  * copy_row_security_policy
+  *
+  * It construct a row-security subquery instead of raw COPY TO statement,
+  * if target relation has a row-level security policy
+  */
+ bool
+ copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+ {
+ 	Expr		  *quals;
+ 	int			   flags;
+ 	Query		  *parse;
+ 	RangeTblEntry  *rte;
+ 	RangeTblRef	   *rtr;
+ 	TargetEntry	   *tle;
+ 	Var			   *var;
+ 	ListCell	   *cell;
+ 
+ 	if (stmt->is_from)
+ 		return false;
+ 
+ 	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+ 	if (!quals)
+ 		return false;
+ 
+ 	parse = (Query *) makeNode(Query);
+ 	parse->commandType = CMD_SELECT;
+ 	parse->querySource = QSRC_ROW_SECURITY;
+ 
+ 	rte = makeNode(RangeTblEntry);
+ 	rte->rtekind = RTE_RELATION;
+ 	rte->relid = RelationGetRelid(rel);
+ 	rte->relkind = RelationGetForm(rel)->relkind;
+ 
+ 	foreach (cell, attnums)
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 		AttrNumber	attno = lfirst_int(cell);
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(RelationGetRelid(rel)),
+ 								Int16GetDatum(attno));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+ 				 attno, RelationGetRelationName(rel));
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		var = makeVar((Index) 1,
+ 					  attform->attnum,
+ 					  attform->atttypid,
+ 					  attform->atttypmod,
+ 					  attform->attcollation,
+ 					  0);
+ 		tle = makeTargetEntry((Expr *) var,
+ 							  list_length(parse->targetList) + 1,
+ 							  pstrdup(NameStr(attform->attname)),
+ 							  false);
+ 		parse->targetList = lappend(parse->targetList, tle);
+ 
+ 		ReleaseSysCache(tuple);
+ 
+ 		rte->selectedCols = bms_add_member(rte->selectedCols,
+ 								attno - FirstLowInvalidHeapAttributeNumber);
+ 	}
+ 	rte->inFromCl = true;
+ 	rte->requiredPerms = ACL_SELECT;
+ 
+ 	rtr = makeNode(RangeTblRef);
+ 	rtr->rtindex = 1;
+ 
+ 	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+ 	parse->rtable = list_make1(rte);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		parse->hasSubLinks = true;
+ 
+ 	stmt->query = (Node *) parse;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * apply_row_security_relation
+  *
+  * It applies row-security policy on a particular relation being specified.
+  * If this relation is top of the inheritance tree, it also checks inherited
+  * children.
+  */
+ static bool
+ apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+ 							CmdType cmd, Index rtindex)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Relation		rel;
+ 	Expr		   *qual;
+ 	int				flags;
+ 	bool			result = false;
+ 
+ 	if (!rte->inh)
+ 	{
+ 		rel = heap_open(rte->relid, NoLock);
+ 		qual = pull_row_security_policy(cmd, rel, &flags);
+ 		if (qual)
+ 		{
+ 			vartrans[rtindex]
+ 				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+ 			if (parse->resultRelation == rtindex)
+ 				parse->sourceRelation = vartrans[rtindex];
+ 			result = true;
+ 		}
+ 		heap_close(rel, NoLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * In case when relation has inherited children, we try to apply
+ 		 * row-level security policy of them if configured.
+ 		 * In addition to regular replacement with a sub-query, we need
+ 		 * to adjust rtindex of AppendRelInfo and varno of translated_vars.
+ 		 * It makes sub-queries perform like regular relations being
+ 		 * inherited from a particular parent relation. So, a table scan
+ 		 * may have underlying a relation scan and two sub-query scans for
+ 		 * instance. If it is result relation of UPDATE or DELETE command,
+ 		 * rtindex to the original relation (regular relation) has to be
+ 		 * kept because sub-query cannot perform as an updatable relation.
+ 		 * So, we save it on child_result of AppendRelInfo; that shall be
+ 		 * used to track relations to be modified at inheritance_planner().
+ 		 */
+ 		ListCell   *lc1, *lc2;
+ 
+ 		foreach (lc1, root->append_rel_list)
+ 		{
+ 			AppendRelInfo  *apinfo = lfirst(lc1);
+ 
+ 			if (apinfo->parent_relid != rtindex)
+ 				continue;
+ 
+ 			if (apply_row_security_relation(root, vartrans, cmd,
+ 											apinfo->child_relid))
+ 			{
+ 				/*
+ 				 * Save the rtindex of actual relation to be modified,
+ 				 * if parent relation is result relation of this query.
+ 				 */
+ 				if (parse->resultRelation == rtindex)
+ 					apinfo->child_result = apinfo->child_relid;
+ 
+ 				apinfo->child_relid = vartrans[apinfo->child_relid];
+ 				/* Adjust varno to reference pseudo columns */
+ 				foreach (lc2, apinfo->translated_vars)
+ 				{
+ 					Var	   *var = lfirst(lc2);
+ 
+ 					if (var)
+ 						var->varno = apinfo->child_relid;
+ 				}
+ 				result = true;
+ 			}
+ 		}
+ 	}
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_recursive
+  *
+  * It walks on the given join-tree to replace relations with row-level
+  * security policy by a simple sub-query.
+  */
+ static bool
+ apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+ {
+ 	bool	result = false;
+ 
+ 	if (jtnode == NULL)
+ 		return false;
+ 	if (IsA(jtnode, RangeTblRef))
+ 	{
+ 		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+ 		Query		   *parse = root->parse;
+ 		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 		CmdType			cmd;
+ 
+ 		/* Only relation can have row-security policy */
+ 		if (rte->rtekind != RTE_RELATION)
+ 			return false;
+ 
+ 		/*
+ 		 * Prevents infinite recursion. Please note that rtindex == 1
+ 		 * of the row-security subquery is a relation being already
+ 		 * processed on the upper level.
+ 		 */
+ 		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+ 			return false;
+ 
+ 		/* Is it a result relation of UPDATE or DELETE command? */
+ 		if (parse->resultRelation == rtindex)
+ 			cmd = parse->commandType;
+ 		else
+ 			cmd = CMD_SELECT;
+ 
+ 		/* Try to apply row-security policy, if configured */
+ 		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+ 	}
+ 	else if (IsA(jtnode, FromExpr))
+ 	{
+ 		FromExpr   *f = (FromExpr *) jtnode;
+ 		ListCell   *l;
+ 
+ 		foreach (l, f->fromlist)
+ 		{
+ 			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+ 				result = true;
+ 		}
+ 	}
+ 	else if (IsA(jtnode, JoinExpr))
+ 	{
+ 		JoinExpr   *j = (JoinExpr *) jtnode;
+ 
+ 		if (apply_row_security_recursive(root, vartrans, j->larg))
+ 			result = true;
+ 		if (apply_row_security_recursive(root, vartrans, j->rarg))
+ 			result = true;
+ 	}
+ 	else
+ 		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+ 
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_policy
+  *
+  * Entrypoint to apply configured row-security policy of the relation.
+  *
+  * In case when the supplied query references relations with row-security
+  * policy, its RangeTblEntry shall be replaced by a row-security subquery
+  * that has simple scan on the referenced table with policy qualifiers.
+  * Of course, security-barrier shall be set on the subquery to prevent
+  * unexpected push-down of functions without leakproof flag.
+  *
+  * For example, when table t1 has a security policy "(x % 2 = 0)", the
+  * following query:
+  *   SELECT * FROM t1 WHERE f_leak(y)
+  * performs as if
+  *   SELECT * FROM (
+  *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+  *   ) AS t1 WHERE f_leak(y)
+  * would be given. Because the sub-query has security barrier flag, 
+  * configured security policy qualifier is always executed prior to
+  * user given functions.
+  */
+ void
+ apply_row_security_policy(PlannerInfo *root)
+ {
+ 	Query	   *parse = root->parse;
+ 	Oid			curr_userid;
+ 	int			curr_seccxt;
+ 	Index	   *vartrans;
+ 
+ 	/*
+ 	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+ 	 * no row-level security policy should be applied regardless
+ 	 * whether it is built-in or extension.
+ 	 */
+ 	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+ 	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+ 		return;
+ 
+ 	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+ 	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+ 	{
+ 		PlannerGlobal  *glob = root->glob;
+ 		PlanInvalItem  *pi_item;
+ 		fixup_varnode_context context;
+ 
+ 		/*
+ 		 * Constructed Plan with row-level security policy depends on
+ 		 * properties of current user (database superuser can bypass
+ 		 * configured row-security policy!), thus, it has to be
+ 		 * invalidated when its assumption was changed.
+ 		 */
+ 		if (!OidIsValid(glob->planUserId))
+ 		{
+ 			/* Plan invalidation on session user-id */
+ 			glob->planUserId = GetUserId();
+ 
+ 			/* Plan invalidation on catalog updates of pg_authid */
+ 			pi_item = makeNode(PlanInvalItem);
+ 			pi_item->cacheId = AUTHOID;
+ 			pi_item->hashValue =
+ 				GetSysCacheHashValue1(AUTHOID,
+ 									  ObjectIdGetDatum(glob->planUserId));
+ 			glob->invalItems = lappend(glob->invalItems, pi_item);
+ 		}
+ 		else
+ 			Assert(glob->planUserId == GetUserId());
+ 
+ 		/*
+ 		 * Var-nodes that referenced RangeTblEntry to be replaced by
+ 		 * row-security sub-query have to be adjusted for appropriate
+ 		 * reference to the underlying pseudo column of the relation.
+ 		 */
+ 		context.root = root;
+ 		context.varlevelsup = 0;
+ 		context.vartrans = vartrans;
+ 		query_tree_walker(parse,
+ 						  fixup_varnode_walker,
+ 						  (void *) &context,
+ 						  QTW_IGNORE_RETURNING);
+ 	}
+ 	pfree(vartrans);
+ }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index 1922097..2c22de8
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 257,262 ****
--- 257,263 ----
  %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
  	   replica_identity
  %type <list>	alter_table_cmds alter_type_cmds
+ %type <str>		row_security_cmd
  
  %type <dbehavior>	opt_drop_behavior
  
*************** alter_table_cmd:
*** 2183,2188 ****
--- 2184,2206 ----
  					n->def = (Node *)$2;
  					$$ = (Node *)n;
  				}
+ 			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+ 			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_SetRowSecurity;
+ 					n->name = $5;
+ 					n->def = (Node *) $8;
+ 					$$ = (Node *)n;
+ 				}
+ 			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+ 			| RESET ROW SECURITY FOR row_security_cmd
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_ResetRowSecurity;
+ 					n->name = $5;
+ 					n->def = NULL;
+ 				}
  			/* ALTER TABLE <name> REPLICA IDENTITY  */
  			| REPLICA IDENTITY_P replica_identity
  				{
*************** reloption_elem:
*** 2293,2298 ****
--- 2311,2322 ----
  				}
  		;
  
+ row_security_cmd: ALL	{ $$ = "all"; }
+ 			| SELECT	{ $$ = "select"; }
+ 			| INSERT	{ $$ = "insert"; }
+ 			| UPDATE	{ $$ = "update"; }
+ 			| DELETE_P	{ $$ = "delete"; }
+ 		;
  
  /*****************************************************************************
   *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
new file mode 100644
index 98cb58a..ac50a2e
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** transformAggregateCall(ParseState *pstat
*** 272,277 ****
--- 272,280 ----
  		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
*** 547,552 ****
--- 550,558 ----
  		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 68b711d..fb09c8c
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformSubLink(ParseState *pstate, Sub
*** 1459,1464 ****
--- 1459,1465 ----
  		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
*** 2640,2645 ****
--- 2641,2648 ----
  			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/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 50cb753..f549293
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** QueryRewrite(Query *parsetree)
*** 3282,3284 ****
--- 3282,3300 ----
  
  	return results;
  }
+ 
+ /*
+  * QueryRewriteExpr
+  *
+  * This routine provides an entry point of query rewriter towards
+  * a certain expression tree with SubLink node; being added after
+  * the top level query rewrite.
+  * It primarily intends to expand views appeared in the qualifiers
+  * appended with row-level security which needs to modify query
+  * tree at head of the planner stage.
+  */
+ void
+ QueryRewriteExpr(Node *node, List *activeRIRs)
+ {
+ 	fireRIRonSubLink(node, activeRIRs);
+ }
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 917130f..b24af71
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3008,3013 ****
--- 3008,3014 ----
  	int			spi_result;
  	Oid			save_userid;
  	int			save_sec_context;
+ 	int			temp_sec_context;
  	Datum		vals[RI_MAX_NUMKEYS * 2];
  	char		nulls[RI_MAX_NUMKEYS * 2];
  
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3087,3094 ****
  
  	/* 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);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
--- 3088,3105 ----
  
  	/* Switch to proper UID to perform check as */
  	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ 
+ 	/*
+ 	 * Row-level security should be disabled in case when 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 (source_is_pk)
+ 		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+ 
  	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! 						   temp_sec_context);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
new file mode 100644
index cf740a9..f1e67a5
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "catalog/namespace.h"
  #include "executor/executor.h"
  #include "executor/spi.h"
+ #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/cost.h"
  #include "optimizer/planmain.h"
*************** CheckCachedPlan(CachedPlanSource *planso
*** 795,800 ****
--- 796,811 ----
  		AcquireExecutorLocks(plan->stmt_list, true);
  
  		/*
+ 		 * If plan was constructed with assumption of a particular user-id,
+ 		 * and it is different from the current one, the cached-plan shall
+ 		 * be invalidated to construct suitable query plan.
+ 		 */
+ 		if (plan->is_valid &&
+ 			OidIsValid(plan->planUserId) &&
+ 			plan->planUserId == GetUserId())
+ 			plan->is_valid = false;
+ 
+ 		/*
  		 * If plan was transient, check to see if TransactionXmin has
  		 * advanced, and if so invalidate it.
  		 */
*************** BuildCachedPlan(CachedPlanSource *planso
*** 847,852 ****
--- 858,865 ----
  {
  	CachedPlan *plan;
  	List	   *plist;
+ 	ListCell   *cell;
+ 	Oid			planUserId = InvalidOid;
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
*************** BuildCachedPlan(CachedPlanSource *planso
*** 914,919 ****
--- 927,950 ----
  		PopActiveSnapshot();
  
  	/*
+ 	 * Check whether the generated plan assumes a particular user-id, or not.
+ 	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+ 	 * it should be kept and used to validation check of the cached plan
+ 	 * under the "current" user-id.
+ 	 */
+ 	foreach (cell, plist)
+ 	{
+ 		PlannedStmt	*pstmt = lfirst(cell);
+ 
+ 		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+ 		{
+ 			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+ 
+ 			planUserId = pstmt->planUserId;
+ 		}
+ 	}
+ 
+ 	/*
  	 * Normally we make a dedicated memory context for the CachedPlan and its
  	 * subsidiary data.  (It's probably not going to be large, but just in
  	 * case, use the default maxsize parameter.  It's transient for the
*************** BuildCachedPlan(CachedPlanSource *planso
*** 956,961 ****
--- 987,993 ----
  	plan->is_oneshot = plansource->is_oneshot;
  	plan->is_saved = false;
  	plan->is_valid = true;
+ 	plan->planUserId = planUserId;
  
  	/* assign generation number to new plan */
  	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index d0acca8..2137e98
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 50,55 ****
--- 50,56 ----
  #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"
*************** RelationBuildDesc(Oid targetRelId, bool 
*** 932,937 ****
--- 933,943 ----
  	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
*** 1840,1845 ****
--- 1846,1853 ----
  		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)
*** 3166,3172 ****
  				relation->rd_rel->relhastriggers = false;
  			restart = true;
  		}
! 
  		/* Release hold on the relation */
  		RelationDecrementReferenceCount(relation);
  
--- 3174,3186 ----
  				relation->rd_rel->relhastriggers = false;
  			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)
*** 4443,4448 ****
--- 4457,4463 ----
  		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 247ad92..9603ef4
*** 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 63a8009..25ff339
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3133,3138 ****
--- 3133,3139 ----
  				 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 224e8cb..7d60cf1
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static char *myFormatType(const char *ty
*** 250,255 ****
--- 250,256 ----
  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);
*************** dumpBlobs(Archive *fout, void *arg)
*** 2714,2719 ****
--- 2715,2848 ----
  	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_rseccmd;
+ 	int			i_rsecqual;
+ 	int			i, j, ntups;
+ 
+ 	/* row-security is not supported prior to v9.4 */
+ 	if (fout->remoteVersion < 90400)
+ 		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.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_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].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;
+ 
+ 	query = createPQExpBuffer();
+ 	delqry = createPQExpBuffer();
+ 	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	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);
+ 	}
+ 	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+ 	appendPQExpBuffer(delqry, "FOR %s;\n", 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)
*** 4242,4247 ****
--- 4371,4377 ----
  	int			i_relhastriggers;
  	int			i_relhasindex;
  	int			i_relhasrules;
+ 	int			i_relhasrowsec;
  	int			i_relhasoids;
  	int			i_relfrozenxid;
  	int			i_toastoid;
*************** getTables(Archive *fout, int *numTables)
*** 4293,4302 ****
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
! 						  "c.relfrozenxid, tc.oid AS toid, "
! 						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "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, "
--- 4423,4429 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
! 						  "c.relhasrowsecurity, "
  						  "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, "
*************** getTables(Archive *fout, int *numTables)
*** 4332,4337 ****
--- 4459,4465 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, c.relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4371,4376 ****
--- 4499,4505 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4408,4413 ****
--- 4537,4543 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4444,4449 ****
--- 4574,4580 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4480,4485 ****
--- 4611,4617 ----
  						  "(%s c.relowner) AS rolname, "
  					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4516,4521 ****
--- 4648,4654 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4552,4557 ****
--- 4685,4691 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4584,4589 ****
--- 4718,4724 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4611,4616 ****
--- 4746,4752 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4648,4653 ****
--- 4784,4790 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4695,4700 ****
--- 4832,4838 ----
  	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_toastoid = PQfnumber(res, "toid");
*************** getTables(Archive *fout, int *numTables)
*** 4744,4749 ****
--- 4882,4888 ----
  		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
*** 7861,7866 ****
--- 8000,8008 ----
  						 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 *
*** 15118,15123 ****
--- 15260,15266 ----
  			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 915e82c..72c0433
*** 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 */
  	Oid			toast_oid;		/* for restore toast frozen xid */
*************** typedef struct _blobInfo
*** 484,489 ****
--- 486,499 ----
  	char	   *blobacl;
  } BlobInfo;
  
+ typedef struct _rowSecurityInfo
+ {
+ 	DumpableObject dobj;
+ 	TableInfo  *rstable;
+ 	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
*** 575,579 ****
--- 585,590 ----
  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 141e713..5c5777c
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** describeDumpableObject(DumpableObject *o
*** 1342,1347 ****
--- 1342,1352 ----
  					 "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/psql/describe.c b/src/bin/psql/describe.c
new file mode 100644
index 96322ca..89e1c09
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** listTables(const char *tabtypes, const c
*** 2752,2757 ****
--- 2752,2761 ----
  		appendPQExpBuffer(&buf,
  			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
  						  gettext_noop("Description"));
+ 		if (pset.sversion >= 90300)
+ 			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
*** 2761,2766 ****
--- 2765,2773 ----
  		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 >= 90300)
+ 		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 3aefbb5..c96e8d4
*** 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 4860e98..04dbeca
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 313,318 ****
--- 313,323 ----
  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(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_class.h b/src/include/catalog/pg_class.h
new file mode 100644
index a1fee11..d937924
*** 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,153 ----
   * 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 29 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 ...798d556
*** a/src/include/catalog/pg_rowsecurity.h
--- b/src/include/catalog/pg_rowsecurity.h
***************
*** 0 ****
--- 1,76 ----
+ /*
+  * 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_rowlevelsec definition. cpp turns this into
+  *		typedef struct FormData_pg_rowlevelsec
+  * ----------------
+  */
+ #define RowSecurityRelationId	5000
+ 
+ CATALOG(pg_rowsecurity,5000)
+ {
+ 	/* Oid of the relation that has row-security policy */
+ 	Oid				rsecrelid;
+ 
+ 	/* One of ROWSECURITY_CMD_* below */
+ 	char			rseccmd;
+ #ifdef CATALOG_VARLEN
+ 	pg_node_tree	rsecqual;
+ #endif
+ } FormData_pg_rowsecurity;
+ 
+ /* ----------------
+  *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+  *		the format of pg_rowlevelsec relation.
+  * ----------------
+  */
+ typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+ 
+ /* ----------------
+  * 		compiler constants for pg_rowlevelsec
+  * ----------------
+  */
+ #define Natts_pg_rowsecurity				3
+ #define Anum_pg_rowsecurity_rsecrelid		1
+ #define Anum_pg_rowsecurity_rseccmd			2
+ #define Anum_pg_rowsecurity_rsecqual		3
+ 
+ #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
+ {
+ 	MemoryContext		rscxt;
+ 	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+ } RowSecurityDesc;
+ 
+ extern void	RelationBuildRowSecurity(Relation relation);
+ extern void	ATExecSetRowSecurity(Relation relation,
+ 								 const char *cmdname, Node *clause);
+ extern void	RemoveRowSecurityById(Oid relationId);
+ 
+ #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
new file mode 100644
index 98ca553..8d76387
*** 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/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 5a40347..4133b8c
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 308,313 ****
--- 308,315 ----
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
   *		projectReturning		for computing a RETURNING list
+  *		rowSecurity				for row-security checks
+  *		rowSecParams			param-list if row-security has SubLink
   * ----------------
   */
  typedef struct ResultRelInfo
*************** typedef struct ResultRelInfo
*** 329,334 ****
--- 331,338 ----
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
  	ProjectionInfo *ri_projectReturning;
+ 	Node	   *ri_rowSecurity;
+ 	List	   *ri_rowSecParams;
  } ResultRelInfo;
  
  /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
new file mode 100644
index fe7cfd3..23c3553
*** a/src/include/nodes/nodeFuncs.h
--- b/src/include/nodes/nodeFuncs.h
***************
*** 24,29 ****
--- 24,30 ----
  #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
  #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
  #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+ #define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
  
  
  extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 6a5555f..8163708
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum QuerySource
*** 31,37 ****
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
--- 31,38 ----
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
! 	QSRC_ROW_SECURITY,			/* added by row-security */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
*************** typedef struct Query
*** 112,118 ****
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
--- 113,121 ----
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 	int			sourceRelation;	/* rtable index of source relation for
! 								 * UPDATE/DELETE, if not identical with
! 								 * resultRelation; 0 for elsewhere */
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
*************** typedef struct RangeTblEntry
*** 739,744 ****
--- 742,752 ----
  	 */
  	Query	   *subquery;		/* the sub-query */
  	bool		security_barrier;		/* is from security_barrier view? */
+ 	Oid			rowsec_relid;	/* OID of the original relation, if this
+ 								 * sub-query originated from row-security
+ 								 * policy on the relation. Elsewhere, it
+ 								 * should be InvalidOid.
+ 								 */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
*************** typedef enum AlterTableType
*** 1314,1319 ****
--- 1322,1329 ----
  	AT_AddOf,					/* OF <type_name> */
  	AT_DropOf,					/* NOT OF */
  	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+ 	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+ 	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
  	AT_GenericOptions			/* OPTIONS (...) */
  } AlterTableType;
  
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 101e22c..e675e87
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct PlannedStmt
*** 67,72 ****
--- 67,74 ----
  	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
  
  	int			nParamExec;		/* number of PARAM_EXEC Params used */
+ 
+ 	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 6d7b594..ee454fa
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerGlobal
*** 98,103 ****
--- 98,105 ----
  	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
  
  	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+ 
+ 	Oid			planUserId;		/* User-Id to be assumed on this plan */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
*************** typedef struct AppendRelInfo
*** 1435,1440 ****
--- 1437,1446 ----
  	 */
  	Index		parent_relid;	/* RT index of append parent rel */
  	Index		child_relid;	/* RT index of append child rel */
+ 	Index		child_result;	/* RT index of append child rel's source,
+ 								 * if source of result relation is not
+ 								 * identical. Elsewhere, 0.
+ 								 */
  
  	/*
  	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index ...ff4dd9a
*** a/src/include/optimizer/rowsecurity.h
--- b/src/include/optimizer/rowsecurity.h
***************
*** 0 ****
--- 1,27 ----
+ /* -------------------------------------------------------------------------
+  *
+  * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+ 									 Relation relation, List *attnums);
+ extern void	apply_row_security_policy(PlannerInfo *root);
+ 
+ #endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
new file mode 100644
index bea3b07..3910853
*** 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/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index c959590..ef10f56
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/parsenodes.h"
  
  extern List *QueryRewrite(Query *parsetree);
+ extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
  extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
  
  extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
new file mode 100644
index 72f8491..35819fa
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
*************** typedef struct CachedPlan
*** 128,133 ****
--- 128,135 ----
  	bool		is_oneshot;		/* is it a "oneshot" plan? */
  	bool		is_saved;		/* is CachedPlan in a long-lived context? */
  	bool		is_valid;		/* is the stmt_list currently valid? */
+ 	Oid			planUserId;		/* is user-id that is assumed on this cached
+ 								   plan, or InvalidOid if portable for anybody */
  	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
  								 * changes from this value */
  	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 21d5871..9a324ab
*** 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
*** 109,114 ****
--- 110,116 ----
  	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 */
  
  	/*
  	 * The index chosen as the relation's replication identity or
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index ...04591df
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
***************
*** 0 ****
--- 1,950 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ 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);
+ 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
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+ (4 rows)
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+   11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+   33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+   44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ERROR:  must be owner of relation document
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ ERROR:  must be owner of relation document
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 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'); -- failed
+ ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+ DETAIL:  Key (cid)=(33) is not present in table "category".
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ 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;
+ 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);
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+ SELECT * FROM t1;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ SELECT * FROM t1 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.b)
+          ->  Seq Scan on t1 t1_1
+                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)
+ 
+ -- reference to system column
+ SELECT oid, * FROM t1;
+  oid | a |  b  
+ -----+---+-----
+  102 | 2 | bbb
+  104 | 4 | ddd
+  201 | 1 | abc
+  203 | 3 | cde
+  301 | 1 | xxx
+  302 | 2 | yyy
+  303 | 3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- reference to whole-row reference
+ SELECT *,t1 FROM t1;
+  a |  b  |   t1    
+ ---+-----+---------
+  2 | bbb | (2,bbb)
+  4 | ddd | (4,ddd)
+  1 | abc | (1,abc)
+  3 | cde | (3,cde)
+  1 | xxx | (1,xxx)
+  2 | yyy | (2,yyy)
+  3 | zzz | (3,zzz)
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT *,t1 FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                ->  Seq Scan on t1 t1_1
+                      Filter: ((a % 2) = 0)
+          ->  Subquery Scan on t2
+                ->  Seq Scan on t2 t2_1
+                      Filter: ((a % 2) = 1)
+          ->  Seq Scan on t3
+ (9 rows)
+ 
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  Seq Scan on t1 t1_1
+                      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)
+ (12 rows)
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ 2	bbb
+ 4	ddd
+ COPY t1 TO stdout WITH OIDS;
+ 102	2	bbb
+ 104	4	ddd
+ COPY t2(c,b) TO stdout WITH OIDS;
+ 201	1.1	abc
+ 203	3.3	cde
+ COPY (SELECT * FROM t1) TO stdout;
+ 2	bbb
+ 4	ddd
+ 1	abc
+ 3	cde
+ 1	xxx
+ 2	yyy
+ 3	zzz
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ ERROR:  table "document" does not have OIDs
+ --
+ -- 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%';
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ ERROR:  infinite recursion detected for relation "s1"
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                      ->  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)
+ (11 rows)
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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
+                      ->  Subquery Scan on s2
+                            Filter: (s2.y ~~ '%af%'::text)
+                            ->  Seq Scan on s2 s2_1
+                                  Filter: ((x % 2) = 0)
+ (11 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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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 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
+  1 | abc
+  1 | xxx
+  2 | yyy
+ (4 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p1(2);
+                      QUERY PLAN                     
+ ----------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a <= 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a <= 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a <= 2)
+ (9 rows)
+ 
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ 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;
+ EXECUTE p2(2);
+  a |  b  
+ ---+-----
+  2 | bbb
+  2 | yyy
+ (2 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p2(2);
+                     QUERY PLAN                     
+ ---------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a = 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a = 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a = 2)
+ (9 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
+    ->  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)
+ 
+ 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
+    ->  Subquery Scan on t1_1
+          Filter: f_leak(t1_1.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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+  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 |             | 
+ (8 rows)
+ 
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+ DROP SCHEMA rls_regress_schema CASCADE;
+ NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index a62a3e3..a41344b
*** 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 5758b07..f7963fc
*** 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 rowsecurity security_label collate matview lock replica_identity
  
  # ----------
  # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 78348f5..51c4c12
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: delete
*** 94,99 ****
--- 94,100 ----
  test: namespace
  test: prepared_xacts
  test: privileges
+ test: rowsecurity
  test: security_label
  test: collate
  test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ...55d4aad
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
***************
*** 0 ****
--- 1,298 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ 
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ 
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 
+ -- 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'); -- failed
+ 
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM document;
+ SELECT * FROM category;
+ 
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ 
+ 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);
+ 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
+ \.
+ 
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ COPY t1 TO stdout WITH OIDS;
+ COPY t2(c,b) TO stdout WITH OIDS;
+ COPY (SELECT * FROM t1) TO stdout;
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ 
+ --
+ -- 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%';
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- OK
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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;
+ 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;
+ 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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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;

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/plain] rlsdemo-v1.sql (2.3K, ../../52ABCF6C.4010409@gmail.com/2-rlsdemo-v1.sql)
  download | inline:
-- Remove changes TO allow re-running the example
DROP TABLE IF EXISTS colors_shapes;
DROP ROLE alpha; 
DROP ROLE beta;
DROP ROLE gamma;
DROP ROLE blue_role;
DROP ROLE purple_role;
DROP ROLE triangle_role;
DROP ROLE heart_role;
DROP ROLE circle_role;
DROP ROLE square_role;

-- Create real user roles
CREATE ROLE alpha;
CREATE ROLE beta;
CREATE ROLE gamma;

-- Populate the sample table
CREATE table colors_shapes (id integer, color varchar(20), shape varchar(20));
INSERT INTO colors_shapes values (1,'blue_role','triangle_role');
INSERT INTO colors_shapes values (2, 'red_role', 'heart_role');
INSERT INTO colors_shapes values (3, 'purple_role', 'circle_role');

-- Create permissions granting roles and grant a subset of them to the users
CREATE ROLE blue_role;
CREATE ROLE red_role;
CREATE ROLE purple_role;
CREATE ROLE triangle_role;
CREATE ROLE heart_role;
CREATE ROLE circle_role;
CREATE ROLE square_role;

GRANT blue_role TO alpha;
GRANT purple_role TO alpha;
GRANT purple_role TO beta;
GRANT triangle_role TO alpha;
GRANT circle_role TO alpha;
GRANT square_role TO alpha;
GRANT red_role TO gamma;
GRANT heart_role TO gamma;

-- Only allow viewing a row if the user has a matching <color>_role and <shape>_role entry
ALTER TABLE colors_shapes
  SET ROW SECURITY FOR ALL TO (
  color IN (
    SELECT permission_role.rolname FROM pg_roles active_role, pg_auth_members, pg_roles permission_role WHERE
      active_role.rolname = current_user AND
      active_role.oid = pg_auth_members.member AND
      permission_role.oid = pg_auth_members.roleid
    )
  AND shape IN (
    SELECT permission_role.rolname FROM pg_roles active_role, pg_auth_members, pg_roles permission_role WHERE
      active_role.rolname = current_user AND
      active_role.oid = pg_auth_members.member AND
      permission_role.oid = pg_auth_members.roleid
    )
);
GRANT ALL ON colors_shapes TO alpha;
GRANT ALL ON colors_shapes TO beta;
GRANT ALL ON colors_shapes TO gamma;
SELECT relname,relhasrowsecurity FROM pg_class WHERE relhasrowsecurity;

-- Show the full working data set as superuser
SELECT * FROM colors_shapes;

-- Demonstrate the three views 
SET SESSION AUTHORIZATION alpha;
SELECT current_user, * FROM colors_shapes;
SET SESSION AUTHORIZATION beta;
SELECT current_user, * FROM colors_shapes;
SET SESSION AUTHORIZATION gamma;
SELECT current_user, * FROM colors_shapes;

  [text/plain] rls-gs-1.patch (169.3K, ../../52ABCF6C.4010409@gmail.com/3-rls-gs-1.patch)
  download | inline diff:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
new file mode 100644
index acc261c..631c5d2
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 234,239 ****
--- 234,244 ----
       </row>
  
       <row>
+       <entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecuritylevelsec</structname></link></entry>
+       <entry>row-level security policy of relation</entry>
+      </row>
+ 
+      <row>
        <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
        <entry>security labels on database objects</entry>
       </row>
***************
*** 1855,1860 ****
--- 1860,1875 ----
       </row>
  
       <row>
+       <entry><structfield>relhasrowsecurity</structfield></entry>
+       <entry><type>bool</type></entry>
+       <entry></entry>
+       <entry>
+        True if table has row-security policy; see
+        <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+       </entry>
+      </row>
+ 
+      <row>
        <entry><structfield>relhassubclass</structfield></entry>
        <entry><type>bool</type></entry>
        <entry></entry>
***************
*** 5135,5140 ****
--- 5150,5205 ----
  
   </sect1>
  
+  <sect1 id="catalog-pg-rowsecurity">
+   <title><structname>pg_rowsecurity</structname></title>
+ 
+   <indexterm zone="catalog-pg-rowsecurity">
+    <primary>pg_rowsecurity</primary>
+   </indexterm>
+   <para>
+    The catalog <structname>pg_rowsecurity</structname> stores expression
+    tree of row-security policy to be performed on a particular relation.
+   </para>
+   <table>
+    <title><structname>pg_rowsecurity</structname> Columns</title>
+    <tgroup cols="4">
+     <thead>
+      <row>
+       <entry>Name</entry>
+       <entry>Type</entry>
+       <entry>References</entry>
+       <entry>Description</entry>
+      </row>
+     </thead>
+     <tbody>
+      <row>
+       <entry><structfield>rsecrelid</structfield></entry>
+       <entry><type>oid</type></entry>
+       <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+       <entry>The table this row-security is for</entry>
+      </row>
+      <row>
+       <entry><structfield>rseccmd</structfield></entry>
+       <entry><type>char</type></entry>
+       <entry></entry>
+       <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+      </row>
+      <row>
+       <entry><structfield>rsecqual</structfield></entry>
+       <entry><type>pg_node_tree</type></entry>
+       <entry></entry>
+       <entry>An expression tree to be performed as row-security policy</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+   <note>
+    <para>
+     <literal>pg_class.relhasrowsecurity</literal>
+     must be true if a table has row-security policy in this catalog.
+    </para>
+   </note>
+  </sect1>
  
   <sect1 id="catalog-pg-seclabel">
    <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
new file mode 100644
index 89649a2..79f37d8
*** a/doc/src/sgml/ref/alter_table.sgml
--- b/doc/src/sgml/ref/alter_table.sgml
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 69,74 ****
--- 69,76 ----
      NOT OF
      OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
      SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+     SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+     RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
      REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
  
  <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 76,81 ****
--- 78,85 ----
      [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
      { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
      [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+ <phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+     { ALL | SELECT | INSERT | UPDATE | DELETE }
  </synopsis>
   </refsynopsisdiv>
  
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 581,586 ****
--- 585,610 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+     <listitem>
+      <para>
+       This form set row-level security policy of the table.
+       Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+       as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+       clause, although mechanism guarantees to evaluate this condition earlier
+       than any other user given condition.
+       <literal>ALL</> is the only supported command type right now.
+       See also <xref linkend="ROW-SECURITY">.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
+     <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+     <listitem>
+      <para>
+       This form reset row-level security policy of the table, if exists.
+       <literal>ALL</> is the only supported command type right now.
      <term><literal>REPLICA IDENTITY</literal></term>
      <listitem>
       <para>
*************** ALTER TABLE [ IF EXISTS ] <replaceable c
*** 840,845 ****
--- 864,883 ----
        </listitem>
       </varlistentry>
  
+      <varlistentry>
+       <term><replaceable class="PARAMETER">condition</replaceable></term>
+       <listitem>
+        <para>
+         An expression that returns a value of type boolean. Expect for a case
+         when queries are executed with superuser privilege, only rows for which
+         this expression returns true will be fetched, updated or deleted.
+         This expression can reference columns of the relation being configured.
+         Sub-queries can be contained within expression tree, unless referenced
+         relation recursively references the same relation.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
      </variablelist>
   </refsect1>
  
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
new file mode 100644
index 177ac7a..c764189
*** a/doc/src/sgml/user-manag.sgml
--- b/doc/src/sgml/user-manag.sgml
*************** DROP ROLE <replaceable>name</replaceable
*** 439,442 ****
--- 439,587 ----
    </para>
   </sect1>
  
+  <sect1 id="row-security">
+   <title>Row-security</title>
+   <para>
+    <productname>PostgreSQL</productname> v9.4 or later provides
+    row-security feature, like several commercial database
+    management system. It allows table owner to assign a particular
+    condition that performs as a security policy of the table; only
+    rows that satisfies the condition should be visible, except for
+    a case when superuser runs queries.
+   </para>
+   <para>
+    Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+    command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+    form that returns a value of type boolean. This expression can contain
+    references to columns of the relation, so it enables to construct
+    arbitrary rule to make access control decision based on contents of
+    each rows.
+   </para>
+   <para>
+    For example, the following <literal>customer</literal> table has
+    <literal>uname</literal> field to store user name, and it assume
+    we don't want to expose any properties of other customers.
+    The following command set <literal>current_user = uname</literal>
+    as row-security policy on the <literal>customer</literal> table.
+ <screen>
+ postgres=> ALTER TABLE customer SET ROW SECURITY
+                                 FOR ALL TO (current_user = uname);
+ ALTER TABLE
+ </screen>
+    <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+    works on the supplied query.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                  QUERY PLAN
+ --------------------------------------------
+  Subquery Scan on customer
+    Filter: f_leak(customer.upasswd)
+    ->  Seq Scan on customer customer_1
+          Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+    This query execution plan means the preconfigured row-security policy is
+    implicitly added, and scan plan on the target relation being wrapped up
+    with a sub-query.
+    It ensures user given qualifiers, including functions with side effects,
+    are never executed earlier than the row-security policy regardless of
+    its cost, except for the cases when these were fully leakproof.
+    This design helps to tackle the scenario described in
+    <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+    qualifiers is significant to keep confidentiality of invisible rows.
+   </para>
+ 
+   <para>
+    On the other hand, this design allows superusers to bypass checks with
+    row-security.
+    It ensures <application>pg_dump</application> can obtain a complete set
+    of database backup, and avoid to execute Trojan horse trap, being injected
+    as a row-security policy of user-defined table, with privileges of
+    superuser.
+   </para>
+ 
+   <para>
+    In case of queries on inherited tables, row-security policy of the parent
+    relation is not applied to child relations. Scope of the row-security
+    policy is limited to the relation on which it is set.
+ <screen>
+ postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+              QUERY PLAN
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.y)
+          ->  Seq Scan on t1 t1_1
+                Filter: ((x % 2) = 0)
+    ->  Seq Scan on t2
+          Filter: f_leak(y)
+    ->  Subquery Scan on t3
+          Filter: f_leak(t3.y)
+          ->  Seq Scan on t3 t3_1
+                Filter: ((x % 2) = 1)
+ (11 rows)
+ </screen>
+     In the above example, <literal>t1</literal> has inherited
+     child table <literal>t2</literal> and <literal>t3</literal>,
+     and row-security policy is set on only <literal>t1</literal>,
+     and <literal>t3</literal>, not <literal>t2</literal>.
+ 
+     The row-security policy of <literal>t1</literal>, <literal>x</literal>
+     must be even-number, is appended only <literal>t1</literal>,
+     neither <literal>t2</literal> nor <literal>t3</literal>.
+     On the contrary, <literal>t3</literal> has different row-security policy;
+     <literal>x</literal> must be odd-number.
+   </para>
+ 
+   <para>
+    Row-security feature also works to queries for writer-operations; such as
+    <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+    <xref linkend="SQL-DELETE"> commands.
+    It ensures all the modified rows satisfies configured row-security policy.
+    The below query tries to update e-mail address of the
+    <literal>customer</> table, and configured row-security makes sure all the
+    modified rows's <literal>uname</> field has to match with
+    <literal>current_user</>.
+   </para>
+ <screen>
+ postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                     QUERY PLAN
+ --------------------------------------------------
+  Update on customer
+    ->  Subquery Scan on customer
+          ->  Seq Scan on customer customer_1
+                Filter: ("current_user"() = uname)
+ (4 rows)
+ </screen>
+   <para>
+    Modification of a certain table is consist of two different stuffs; one
+    is fetch rows to be modified from the result relation (except for
+    <literal>INSERT</> command), second is insertion of rows to the
+    result relation. Usually, before-row trigger or check constraints are
+    run in the second phase. In addition, row-security policy is also checked
+    on this stage, to prevent to insert or update rows with unprivileged
+    values.
+   </para>
+ 
+   <para>
+    Unlike other commercial database systems, we don't have any plan to allow
+    individual row-security policy for each command type. Even if we want to
+    perform with difference policy between <xref linkend="SQL-SELECT"> and
+    <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+    to be invisible using <xref linkend="SQL-DELETE"> command.
+   </para>
+ 
+   <para>
+    Even though it is not a specific matter in row-security, please be careful
+    if you plan to use <literal>current_user</> in row-level security policy.
+    <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+    switch current user identifier during execution of the query.
+    Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+    then remaining rows with privilege of <literal>bob</>. If and when query
+    execution plan contains some kind of materialization and row-security
+    policy contains <literal>current_user</>, the fetched tuples in
+    <literal>bob</>'s screen might be evaluated according to the privilege of
+    <literal>alice</>.
+   </para>
+  </sect1>
  </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
new file mode 100644
index a974bd5..beb73df
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
*************** OBJS = catalog.o dependency.o heap.o ind
*** 15,21 ****
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
--- 15,21 ----
         pg_constraint.o pg_conversion.o \
         pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
         pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
!        pg_rowsecurity.o pg_type.o storage.o toasting.o
  
  BKIFILES = postgres.bki postgres.description postgres.shdescription
  
*************** 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/dependency.c b/src/backend/catalog/dependency.c
new file mode 100644
index 908126c..3590ec5
*** 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"
*************** doDeletion(const ObjectAddress *object, 
*** 1249,1254 ****
--- 1250,1259 ----
  			RemoveEventTriggerById(object->objectId);
  			break;
  
+ 		case OCLASS_ROWSECURITY:
+ 			RemoveRowSecurityById(object->objectId);
+ 			break;
+ 
  		default:
  			elog(ERROR, "unrecognized object class: %u",
  				 object->classId);
*************** getObjectClass(const ObjectAddress *obje
*** 2316,2321 ****
--- 2321,2329 ----
  
  		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 6f2e142..1ad6955
*** a/src/backend/catalog/heap.c
--- b/src/backend/catalog/heap.c
*************** InsertPgClassTuple(Relation pg_class_des
*** 798,803 ****
--- 798,804 ----
  	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 9011190..e66c91b
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
*************** getObjectDescription(const ObjectAddress
*** 2143,2148 ****
--- 2143,2198 ----
  				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, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index ...34d33e8
*** a/src/backend/catalog/pg_rowsecurity.c
--- b/src/backend/catalog/pg_rowsecurity.c
***************
*** 0 ****
--- 1,337 ----
+ /* -------------------------------------------------------------------------
+  *
+  * pg_rowsecurity.c
+  *    routines to support manipulation of the pg_rowsecurity catalog
+  *
+  * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1994, Regents of the University of California
+  *
+  * -------------------------------------------------------------------------
+  */
+ #include "postgres.h"
+ #include "access/genam.h"
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/pg_class.h"
+ #include "catalog/pg_rowsecurity.h"
+ #include "catalog/pg_type.h"
+ #include "nodes/nodeFuncs.h"
+ #include "optimizer/clauses.h"
+ #include "parser/parse_clause.h"
+ #include "parser/parse_node.h"
+ #include "parser/parse_relation.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/inval.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "utils/tqual.h"
+ 
+ /*
+  * Load row-security policy from the catalog, and keep it on
+  * 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();
+ 	{
+ 		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+ 		{
+ 			Datum	value;
+ 			bool	isnull;
+ 			char   *temp;
+ 
+ 			if (!rsdesc)
+ 			{
+ 				rscxt = AllocSetContextCreate(CacheMemoryContext,
+ 											  "Row-security descriptor",
+ 											  ALLOCSET_SMALL_MINSIZE,
+ 											  ALLOCSET_SMALL_INITSIZE,
+ 											  ALLOCSET_SMALL_MAXSIZE);
+ 				oldcxt = MemoryContextSwitchTo(rscxt);
+ 				rsdesc = palloc0(sizeof(RowSecurityDesc));
+ 				rsdesc->rscxt = rscxt;
+ 				MemoryContextSwitchTo(oldcxt);
+ 			}
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 
+ 			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+ 			{
+ 				ereport(WARNING,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("Per-command row-security not implemented")));
+ 				continue;
+ 			}
+ 
+ 			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+ 								 RelationGetDescr(catalog), &isnull);
+ 			Assert(!isnull);
+ 			temp = TextDatumGetCString(value);
+ 
+ 			oldcxt = MemoryContextSwitchTo(rscxt);
+ 			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+ 			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+ 			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+ 			rsdesc->rsall.hassublinks
+ 				= contain_subplans((Node *)rsdesc->rsall.qual);
+ 			MemoryContextSwitchTo(oldcxt);
+ 
+ 			pfree(temp);
+ 		}
+ 	}
+ 	PG_CATCH();
+ 	{
+ 		if (rscxt != NULL)
+ 			MemoryContextDelete(rscxt);
+ 		PG_RE_THROW();
+ 	}
+ 	PG_END_TRY();
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, AccessShareLock);
+ 
+ 	relation->rsdesc = rsdesc;
+ }
+ 
+ /*
+  * Parse the supplied row-security policy, and insert/update a row
+  * of pg_rowsecurity catalog.
+  */
+ static void
+ InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+ {
+ 	Oid				relationId = RelationGetRelid(relation);
+ 	Oid				rowsecId;
+ 	ParseState	   *pstate;
+ 	RangeTblEntry  *rte;
+ 	Node		   *qual;
+ 	Relation		catalog;
+ 	ScanKeyData		skeys[2];
+ 	SysScanDesc		sscan;
+ 	HeapTuple		oldtup;
+ 	HeapTuple		newtup;
+ 	Datum			values[Natts_pg_rowsecurity];
+ 	bool			isnull[Natts_pg_rowsecurity];
+ 	bool			replaces[Natts_pg_rowsecurity];
+ 	ObjectAddress	target;
+ 	ObjectAddress	myself;
+ 
+ 	/* Parse the supplied clause */
+ 	pstate = make_parsestate(NULL);
+ 
+ 	rte = addRangeTableEntryForRelation(pstate, relation,
+ 										NULL, false, false);
+ 	addRTEtoQuery(pstate, rte, false, true, true);
+ 
+ 	qual = transformWhereClause(pstate, copyObject(clause),
+ 								EXPR_KIND_ROW_SECURITY,
+ 								"ROW SECURITY");
+ 	/* zero-clear */
+ 	memset(values,   0, sizeof(values));
+ 	memset(replaces, 0, sizeof(replaces));
+ 	memset(isnull,   0, sizeof(isnull));
+ 
+ 	/* Update or Insert an entry to pg_rowsecurity catalog  */
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skeys[0],
+ 				Anum_pg_rowsecurity_rsecrelid,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(RelationGetRelid(relation)));
+ 	ScanKeyInit(&skeys[1],
+ 				Anum_pg_rowsecurity_rseccmd,
+ 				BTEqualStrategyNumber, F_CHAREQ,
+ 				CharGetDatum(rseccmd));
+ 	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+ 							   NULL, 2, skeys);
+ 	oldtup = systable_getnext(sscan);
+ 	if (HeapTupleIsValid(oldtup))
+ 	{
+ 		rowsecId = HeapTupleGetOid(oldtup);
+ 
+ 		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 
+ 		newtup = heap_modify_tuple(oldtup,
+ 								   RelationGetDescr(catalog),
+ 								   values, isnull, replaces);
+ 		simple_heap_update(catalog, &newtup->t_self, newtup);
+ 
+ 		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+ 	}
+ 	else
+ 	{
+ 		values[Anum_pg_rowsecurity_rsecrelid - 1]
+ 			= ObjectIdGetDatum(relationId);
+ 		values[Anum_pg_rowsecurity_rseccmd - 1]
+ 			= CharGetDatum(rseccmd);
+ 		values[Anum_pg_rowsecurity_rsecqual - 1]
+ 			= CStringGetTextDatum(nodeToString(qual));
+ 		newtup = heap_form_tuple(RelationGetDescr(catalog),
+ 								 values, isnull);
+ 		rowsecId = simple_heap_insert(catalog, newtup);
+ 	}
+ 	CatalogUpdateIndexes(catalog, newtup);
+ 
+ 	heap_freetuple(newtup);
+ 
+ 	/* records dependencies of row-security policy and relation/columns */
+ 	target.classId = RelationRelationId;
+ 	target.objectId = relationId;
+ 	target.objectSubId = 0;
+ 
+ 	myself.classId = RowSecurityRelationId;
+ 	myself.objectId = rowsecId;
+ 	myself.objectSubId = 0;
+ 
+ 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+ 
+ 	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+ 						   DEPENDENCY_NORMAL);
+ 	free_parsestate(pstate);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * Remove row-security policy row of pg_rowsecurity
+  */
+ static void
+ DeletePolicyRow(Relation relation, char rseccmd)
+ {
+ 	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+ 
+ 	if (relation->rsdesc)
+ 	{
+ 		ObjectAddress	address;
+ 
+ 		address.classId = RowSecurityRelationId;
+ 		address.objectId = relation->rsdesc->rsall.rsecid;
+ 		address.objectSubId = 0;
+ 
+ 		performDeletion(&address, DROP_RESTRICT, 0);
+ 	}
+ 	else
+ 	{
+ 		/* Nothing to do here */
+ 		elog(INFO, "relation %s has no row-security policy, skipped",
+ 			 RelationGetRelationName(relation));
+ 	}
+ }
+ 
+ /*
+  * Guts of row-security policy deletion.
+  */
+ void
+ RemoveRowSecurityById(Oid rowsecId)
+ {
+ 	Relation	catalog;
+ 	ScanKeyData	skey;
+ 	SysScanDesc	sscan;
+ 	HeapTuple	tuple;
+ 	Relation	rel;
+ 	Oid			relid;
+ 
+ 	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+ 
+ 	ScanKeyInit(&skey,
+ 				ObjectIdAttributeNumber,
+ 				BTEqualStrategyNumber, F_OIDEQ,
+ 				ObjectIdGetDatum(rowsecId));
+ 	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+ 							   NULL, 1, &skey);
+ 	tuple = systable_getnext(sscan);
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+ 
+ 	/*
+ 	 * Open and exclusive-lock the relation the row-security belongs to.
+ 	 */
+ 	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+ 
+ 	rel = heap_open(relid, AccessExclusiveLock);
+ 
+ 	simple_heap_delete(catalog, &tuple->t_self);
+ 
+ 	/* Ensure relcache entries of other session being rebuilt */
+ 	CacheInvalidateRelcache(rel);
+ 
+ 	heap_close(rel, NoLock);
+ 
+ 	systable_endscan(sscan);
+ 	heap_close(catalog, RowExclusiveLock);
+ }
+ 
+ /*
+  * ALTER TABLE <name> SET ROW SECURITY (...) OR
+  *                    RESET ROW SECURITY
+  */
+ void
+ ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+ {
+ 	Oid			relid = RelationGetRelid(relation);
+ 	char		rseccmd;
+ 
+ 	if (strcmp(cmdname, "all") == 0)
+ 		rseccmd = ROWSECURITY_CMD_ALL;
+ 	else
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("Row-security for \"%s\" is not implemented yet",
+ 						cmdname)));
+ 
+ 	if (clause != NULL)
+ 	{
+ 		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+ 
+ 		/*
+ 		 * Also, turn on relhasrowsecurity, if not.
+ 		 */
+ 		if (!RelationGetForm(relation)->relhasrowsecurity)
+ 		{
+ 			Relation	class_rel = heap_open(RelationRelationId,
+ 											  RowExclusiveLock);
+ 			HeapTuple	tuple;
+ 
+ 			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ 			if (!HeapTupleIsValid(tuple))
+ 				elog(ERROR, "cache lookup failed for relation %u", relid);
+ 
+ 			((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);
+ 		}
+ 	}
+ 	else
+ 		DeletePolicyRow(relation, rseccmd);
+ 
+ 	CacheInvalidateRelcache(relation);
+ }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 0fa83a6..d657931
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
***************
*** 24,29 ****
--- 24,30 ----
  #include "access/htup_details.h"
  #include "access/sysattr.h"
  #include "access/xact.h"
+ #include "catalog/heap.h"
  #include "catalog/namespace.h"
  #include "catalog/pg_type.h"
  #include "commands/copy.h"
***************
*** 34,48 ****
--- 35,53 ----
  #include "libpq/pqformat.h"
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
+ #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/planner.h"
+ #include "optimizer/rowsecurity.h"
  #include "parser/parse_relation.h"
+ #include "parser/parsetree.h"
  #include "rewrite/rewriteHandler.h"
  #include "storage/fd.h"
  #include "tcop/tcopprot.h"
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/lsyscache.h"
+ #include "utils/syscache.h"
  #include "utils/memutils.h"
  #include "utils/portal.h"
  #include "utils/rel.h"
*************** DoCopy(const CopyStmt *stmt, const char 
*** 814,819 ****
--- 819,839 ----
  		rel = heap_openrv(stmt->relation,
  						  (is_from ? RowExclusiveLock : AccessShareLock));
  
+ 		tupDesc = RelationGetDescr(rel);
+ 		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+ 
+ 		/*
+ 		 * We have to run regular query, if the target relation has
+  		 * row-level security policy
+ 		 */
+ 		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+ 		{
+ 			heap_close(rel, NoLock);	/* close with keeping lock */
+ 			relid = InvalidOid;
+ 			rel = NULL;
+ 		}
+ 		else
+ 		{
  		relid = RelationGetRelid(rel);
  
  		rte = makeNode(RangeTblEntry);
*************** DoCopy(const CopyStmt *stmt, const char 
*** 822,829 ****
  		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) -
--- 842,847 ----
*************** DoCopy(const CopyStmt *stmt, const char 
*** 835,840 ****
--- 853,859 ----
  				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
  		}
  		ExecCheckRTPerms(list_make1(rte), true);
+ 		}
  	}
  	else
  	{
*************** ProcessCopyOptions(CopyState cstate,
*** 1193,1198 ****
--- 1212,1264 ----
  }
  
  /*
+  * Adjust Query tree constructed with row-level security feature.
+  * If WITH OIDS option was supplied, it adds Var node to reference
+  * object-id system column.
+  */
+ static void
+ fixup_oid_of_rls_query(Query *query)
+ {
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	Var			   *subvar;
+ 	ListCell	   *cell;
+ 	Form_pg_attribute	attform
+ 		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+ 
+ 	subrte = rt_fetch((Index) 1, query->rtable);
+ 	Assert(subrte->rtekind == RTE_RELATION);
+ 
+ 	if (!SearchSysCacheExists2(ATTNUM,
+ 							   ObjectIdGetDatum(subrte->relid),
+ 							   Int16GetDatum(attform->attnum)))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_UNDEFINED_COLUMN),
+ 				 errmsg("table \"%s\" does not have OIDs",
+ 						get_rel_name(subrte->relid))));
+ 
+ 	subvar = makeVar((Index) 1,
+ 					 attform->attnum,
+ 					 attform->atttypid,
+ 					 attform->atttypmod,
+ 					 attform->attcollation,
+ 					 0);
+ 	subtle = makeTargetEntry((Expr *) subvar,
+ 							 0,
+ 							 pstrdup(NameStr(attform->attname)),
+ 							 false);
+ 
+ 	query->targetList = list_concat(list_make1(subtle),
+ 									query->targetList);
+ 	/* adjust resno of TargetEntry */
+ 	foreach (cell, query->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 		subtle->resno++;
+ 	}
+ }
+ 
+ /*
   * Common setup routines used by BeginCopyFrom and BeginCopyTo.
   *
   * Iff <binary>, unload or reload in the binary format, as opposed to the
*************** BeginCopy(bool is_from,
*** 1264,1269 ****
--- 1330,1354 ----
  		Assert(!is_from);
  		cstate->rel = NULL;
  
+ 		/*
+ 		 * In case when regular COPY TO was replaced because of row-level
+ 		 * security, "raw_query" node have already analyzed / rewritten
+ 		 * query tree.
+ 		 */
+ 		if (IsA(raw_query, Query))
+ 		{
+ 			query = (Query *) raw_query;
+ 
+ 			Assert(query->querySource == QSRC_ROW_SECURITY);
+ 			if (cstate->oids)
+ 			{
+ 				fixup_oid_of_rls_query(query);
+ 				cstate->oids = false;
+ 			}
+ 			attnamelist = NIL;
+ 		}
+ 		else
+ 		{
  		/* Don't allow COPY w/ OIDs from a select */
  		if (cstate->oids)
  			ereport(ERROR,
*************** BeginCopy(bool is_from,
*** 1288,1293 ****
--- 1373,1379 ----
  			elog(ERROR, "unexpected rewrite result");
  
  		query = (Query *) linitial(rewritten);
+ 		}
  
  		/* The grammar allows SELECT INTO, but we don't support that */
  		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
new file mode 100644
index 328e2a8..0374629
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
*************** EventTriggerSupportsObjectClass(ObjectCl
*** 992,997 ****
--- 992,998 ----
  		case OCLASS_USER_MAPPING:
  		case OCLASS_DEFACL:
  		case OCLASS_EXTENSION:
+ 		case OCLASS_ROWSECURITY:
  			return true;
  
  		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
new file mode 100644
index 1d9f29a..afe77c7
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 37,42 ****
--- 37,43 ----
  #include "catalog/pg_inherits_fn.h"
  #include "catalog/pg_namespace.h"
  #include "catalog/pg_opclass.h"
+ #include "catalog/pg_rowsecurity.h"
  #include "catalog/pg_tablespace.h"
  #include "catalog/pg_trigger.h"
  #include "catalog/pg_type.h"
*************** AlterTableGetLockLevel(List *cmds)
*** 2789,2794 ****
--- 2790,2797 ----
  			case AT_SetTableSpace:		/* must rewrite heap */
  			case AT_DropNotNull:		/* may change some SQL plans */
  			case AT_SetNotNull:
+ 			case AT_SetRowSecurity:
+ 			case AT_ResetRowSecurity:
  			case AT_GenericOptions:
  			case AT_AlterColumnGenericOptions:
  				cmd_lockmode = AccessExclusiveLock;
*************** ATPrepCmd(List **wqueue, Relation rel, A
*** 3164,3169 ****
--- 3167,3174 ----
  		case AT_DropInherit:	/* NO INHERIT */
  		case AT_AddOf:			/* OF */
  		case AT_DropOf: /* NOT OF */
+ 		case AT_SetRowSecurity:
+ 		case AT_ResetRowSecurity:
  			ATSimplePermissions(rel, ATT_TABLE);
  			/* These commands never recurse */
  			/* No command-specific prep needed */
*************** ATExecCmd(List **wqueue, AlteredTableInf
*** 3449,3454 ****
--- 3454,3464 ----
  		case AT_DropOf:
  			ATExecDropOf(rel, lockmode);
  			break;
+ 		case AT_SetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+ 			break;
+ 		case AT_ResetRowSecurity:
+ 			ATExecSetRowSecurity(rel, cmd->name, NULL);
  		case AT_ReplicaIdentity:
  			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
  			break;
*************** ATExecAlterColumnType(AlteredTableInfo *
*** 7808,7813 ****
--- 7818,7839 ----
  				Assert(defaultexpr);
  				break;
  
+ 			case OCLASS_ROWSECURITY:
+ 				/*
+ 				 * A row-level security policy can depend on a column in case
+ 				 * when the policy clause references a particular column.
+ 				 * Due to same reason why TRIGGER ... WHEN does not support
+ 				 * to change column's type being referenced in clause, row-
+ 				 * level security policy also does not support it.
+ 				 */
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 						 errmsg("cannot alter type of a column used in a row-level security policy"),
+ 						 errdetail("%s depends on column \"%s\"",
+ 								   getObjectDescription(&foundObject),
+ 								   colName)));
+ 				break;
+ 
  			case OCLASS_PROC:
  			case OCLASS_TYPE:
  			case OCLASS_CAST:
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index 6be17a9..92772bc
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** InitPlan(QueryDesc *queryDesc, int eflag
*** 789,796 ****
  	foreach(l, plannedstmt->rowMarks)
  	{
  		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
! 		Oid			relid;
! 		Relation	relation;
  		ExecRowMark *erm;
  
  		/* ignore "parent" rowmarks; they are irrelevant at runtime */
--- 789,797 ----
  	foreach(l, plannedstmt->rowMarks)
  	{
  		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
! 		RangeTblEntry *rte = NULL;
! 		Relation	relation = NULL;
! 		LOCKMODE	lockmode = NoLock;
  		ExecRowMark *erm;
  
  		/* ignore "parent" rowmarks; they are irrelevant at runtime */
*************** InitPlan(QueryDesc *queryDesc, int eflag
*** 803,829 ****
  			case ROW_MARK_NOKEYEXCLUSIVE:
  			case ROW_MARK_SHARE:
  			case ROW_MARK_KEYSHARE:
! 				relid = getrelid(rc->rti, rangeTable);
! 				relation = heap_open(relid, RowShareLock);
  				break;
  			case ROW_MARK_REFERENCE:
! 				relid = getrelid(rc->rti, rangeTable);
! 				relation = heap_open(relid, AccessShareLock);
  				break;
  			case ROW_MARK_COPY:
  				/* there's no real table here ... */
- 				relation = NULL;
  				break;
  			default:
  				elog(ERROR, "unrecognized markType: %d", rc->markType);
- 				relation = NULL;	/* keep compiler quiet */
  				break;
  		}
  
  		/* Check that relation is a legal target for marking */
! 		if (relation)
  			CheckValidRowMarkRel(relation, rc->markType);
! 
  		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
  		erm->relation = relation;
  		erm->rti = rc->rti;
--- 804,836 ----
  			case ROW_MARK_NOKEYEXCLUSIVE:
  			case ROW_MARK_SHARE:
  			case ROW_MARK_KEYSHARE:
! 				rte = rt_fetch(rc->rti, rangeTable);
! 				lockmode = RowShareLock;
  				break;
  			case ROW_MARK_REFERENCE:
! 				rte = rt_fetch(rc->rti, rangeTable);
! 				lockmode = AccessShareLock;
  				break;
  			case ROW_MARK_COPY:
  				/* there's no real table here ... */
  				break;
  			default:
  				elog(ERROR, "unrecognized markType: %d", rc->markType);
  				break;
  		}
  
  		/* Check that relation is a legal target for marking */
! 		if (rte)
! 		{
! 			if (rte->rtekind == RTE_RELATION)
! 				relation = heap_open(rte->relid, lockmode);
! 			else
! 			{
! 				Assert(rte->rtekind == RTE_SUBQUERY);
! 				relation = heap_open(rte->rowsec_relid, lockmode);
! 			}
  			CheckValidRowMarkRel(relation, rc->markType);
! 		}
  		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
  		erm->relation = relation;
  		erm->rti = rc->rti;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index e3edcf6..d5ce3d2
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyAppendRelInfo(const AppendRelInfo *
*** 1934,1939 ****
--- 1934,1940 ----
  
  	COPY_SCALAR_FIELD(parent_relid);
  	COPY_SCALAR_FIELD(child_relid);
+ 	COPY_SCALAR_FIELD(child_result);
  	COPY_SCALAR_FIELD(parent_reltype);
  	COPY_SCALAR_FIELD(child_reltype);
  	COPY_NODE_FIELD(translated_vars);
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1975,1980 ****
--- 1976,1982 ----
  	COPY_SCALAR_FIELD(relkind);
  	COPY_NODE_FIELD(subquery);
  	COPY_SCALAR_FIELD(security_barrier);
+ 	COPY_SCALAR_FIELD(rowsec_relid);
  	COPY_SCALAR_FIELD(jointype);
  	COPY_NODE_FIELD(joinaliasvars);
  	COPY_NODE_FIELD(functions);
*************** _copyQuery(const Query *from)
*** 2463,2468 ****
--- 2465,2471 ----
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_SCALAR_FIELD(resultRelation);
+ 	COPY_SCALAR_FIELD(sourceRelation);
  	COPY_SCALAR_FIELD(hasAggs);
  	COPY_SCALAR_FIELD(hasWindowFuncs);
  	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 1f9b5d7..082d3d4
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalAppendRelInfo(const AppendRelInfo 
*** 812,817 ****
--- 812,818 ----
  {
  	COMPARE_SCALAR_FIELD(parent_relid);
  	COMPARE_SCALAR_FIELD(child_relid);
+ 	COMPARE_SCALAR_FIELD(child_result);
  	COMPARE_SCALAR_FIELD(parent_reltype);
  	COMPARE_SCALAR_FIELD(child_reltype);
  	COMPARE_NODE_FIELD(translated_vars);
*************** _equalQuery(const Query *a, const Query 
*** 847,852 ****
--- 848,854 ----
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
  	COMPARE_SCALAR_FIELD(resultRelation);
+ 	COMPARE_SCALAR_FIELD(sourceRelation);
  	COMPARE_SCALAR_FIELD(hasAggs);
  	COMPARE_SCALAR_FIELD(hasWindowFuncs);
  	COMPARE_SCALAR_FIELD(hasSubLinks);
*************** _equalRangeTblEntry(const RangeTblEntry 
*** 2245,2250 ****
--- 2247,2253 ----
  	COMPARE_SCALAR_FIELD(relkind);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_SCALAR_FIELD(security_barrier);
+ 	COMPARE_SCALAR_FIELD(rowsec_relid);
  	COMPARE_SCALAR_FIELD(jointype);
  	COMPARE_NODE_FIELD(joinaliasvars);
  	COMPARE_NODE_FIELD(functions);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
new file mode 100644
index d7db67d..b9b5aa2
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** query_tree_walker(Query *query,
*** 1941,1948 ****
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (walker((Node *) query->returningList, context))
! 		return true;
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
--- 1941,1951 ----
  		return true;
  	if (walker((Node *) query->withCheckOptions, context))
  		return true;
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 	{
! 		if (walker((Node *) query->returningList, context))
! 			return true;
! 	}
  	if (walker((Node *) query->jointree, context))
  		return true;
  	if (walker(query->setOperations, context))
*************** query_tree_mutator(Query *query,
*** 2678,2684 ****
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	MUTATE(query->returningList, query->returningList, List *);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
--- 2681,2690 ----
  
  	MUTATE(query->targetList, query->targetList, List *);
  	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
! 	if (!(flags & QTW_IGNORE_RETURNING))
! 		MUTATE(query->returningList, query->returningList, List *);
! 	else
! 		query->returningList = copyObject(query->returningList);
  	MUTATE(query->jointree, query->jointree, FromExpr *);
  	MUTATE(query->setOperations, query->setOperations, Node *);
  	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 4c7505e..fde3853
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAppendRelInfo(StringInfo str, const 
*** 1923,1928 ****
--- 1923,1929 ----
  
  	WRITE_UINT_FIELD(parent_relid);
  	WRITE_UINT_FIELD(child_relid);
+ 	WRITE_UINT_FIELD(child_result);
  	WRITE_OID_FIELD(parent_reltype);
  	WRITE_OID_FIELD(child_reltype);
  	WRITE_NODE_FIELD(translated_vars);
*************** _outQuery(StringInfo str, const Query *n
*** 2238,2243 ****
--- 2239,2245 ----
  		appendStringInfoString(str, " :utilityStmt <>");
  
  	WRITE_INT_FIELD(resultRelation);
+ 	WRITE_INT_FIELD(sourceRelation);
  	WRITE_BOOL_FIELD(hasAggs);
  	WRITE_BOOL_FIELD(hasWindowFuncs);
  	WRITE_BOOL_FIELD(hasSubLinks);
*************** _outRangeTblEntry(StringInfo str, const 
*** 2373,2378 ****
--- 2375,2381 ----
  		case RTE_SUBQUERY:
  			WRITE_NODE_FIELD(subquery);
  			WRITE_BOOL_FIELD(security_barrier);
+ 			WRITE_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index 2e2cfa7..9f32519
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readQuery(void)
*** 199,204 ****
--- 199,205 ----
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
  	READ_INT_FIELD(resultRelation);
+ 	READ_INT_FIELD(sourceRelation);
  	READ_BOOL_FIELD(hasAggs);
  	READ_BOOL_FIELD(hasWindowFuncs);
  	READ_BOOL_FIELD(hasSubLinks);
*************** _readRangeTblEntry(void)
*** 1214,1219 ****
--- 1215,1221 ----
  		case RTE_SUBQUERY:
  			READ_NODE_FIELD(subquery);
  			READ_BOOL_FIELD(security_barrier);
+ 			READ_OID_FIELD(rowsec_relid);
  			break;
  		case RTE_JOIN:
  			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 6670794..8b171ce
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 33,38 ****
--- 33,39 ----
  #include "optimizer/planmain.h"
  #include "optimizer/planner.h"
  #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
  #include "optimizer/subselect.h"
  #include "optimizer/tlist.h"
  #include "parser/analyze.h"
*************** standard_planner(Query *parse, int curso
*** 177,182 ****
--- 178,184 ----
  	glob->lastPHId = 0;
  	glob->lastRowMarkId = 0;
  	glob->transientPlan = false;
+ 	glob->planUserId = InvalidOid;
  
  	/* Determine what fraction of the plan is likely to be scanned */
  	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
*************** standard_planner(Query *parse, int curso
*** 254,259 ****
--- 256,262 ----
  	result->relationOids = glob->relationOids;
  	result->invalItems = glob->invalItems;
  	result->nParamExec = glob->nParamExec;
+ 	result->planUserId = glob->planUserId;
  
  	return result;
  }
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 404,409 ****
--- 407,425 ----
  	expand_inherited_tables(root);
  
  	/*
+ 	 * Apply row-security policy of the relation being referenced,
+ 	 * if configured with either of built-in or extension's features.
+ 	 * RangeTblEntry of the relation with row-security policy shall
+ 	 * be replaced with a row-security subquery that has simple scan
+ 	 * on the target relation with row-security policy qualifiers.
+ 	 *
+ 	 * This routine assumes PlannerInfo is already handled with
+ 	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+ 	 * have valid information.
+ 	 */
+ 	apply_row_security_policy(root);
+ 
+ 	/*
  	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
  	 * because preprocess_expression will reduce a constant-true condition to
  	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
*************** inheritance_planner(PlannerInfo *root)
*** 888,893 ****
--- 904,911 ----
  					newrti = list_length(subroot.parse->rtable) + 1;
  					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
  					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
  					rte = copyObject(rte);
  					subroot.parse->rtable = lappend(subroot.parse->rtable,
  													rte);
*************** inheritance_planner(PlannerInfo *root)
*** 951,957 ****
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
--- 969,978 ----
  		root->init_plans = subroot.init_plans;
  
  		/* Build list of target-relation RT indexes */
! 		resultRelations = lappend_int(resultRelations,
! 									  (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid));
  
  		/* Build lists of per-relation WCO and RETURNING targetlists */
  		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
new file mode 100644
index fb67f9e..0cccdf5
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 37,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
   * preprocess_targetlist
--- 37,85 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 							   Index result_relation, Index source_relation,
! 							   List *range_table);
! 
! /*
!  * lookup_varattno
!  *
!  * This routine returns an attribute number to reference a particular
!  * attribute. In case when the target relation is really relation,
!  * we can reference arbitrary attribute (including system column)
!  * without any translations. However, we have to translate varattno
!  * of Var that references sub-queries being originated from regular
!  * relations with row-level security policy due to nature of sub-query
!  * that has no system-column.
!  */
! static AttrNumber
! lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
! {
! 	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
! 
! 	if (rte->rtekind == RTE_SUBQUERY &&
! 		rte->subquery->querySource == QSRC_ROW_SECURITY)
! 	{
! 		ListCell   *cell;
! 
! 		foreach (cell, rte->subquery->targetList)
! 		{
! 			TargetEntry *tle = lfirst(cell);
! 			Var			*var;
! 
! 			if (IsA(tle->expr, Const))
! 				continue;
! 
! 			var = (Var *) tle->expr;
! 			Assert(IsA(var, Var));
  
+ 			if (var->varattno == attno)
+ 				return tle->resno;
+ 		}
+ 		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+ 			 attno);
+ 	}
+ 	return attno;
+ }
  
  /*
   * preprocess_targetlist
*************** preprocess_targetlist(PlannerInfo *root,
*** 51,56 ****
--- 92,98 ----
  {
  	Query	   *parse = root->parse;
  	int			result_relation = parse->resultRelation;
+ 	int			source_relation = parse->sourceRelation;
  	List	   *range_table = parse->rtable;
  	CmdType		command_type = parse->commandType;
  	ListCell   *lc;
*************** preprocess_targetlist(PlannerInfo *root,
*** 73,80 ****
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
--- 115,126 ----
  	 * 10/94
  	 */
  	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 	{
  		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation,
! 								  source_relation,
! 								  range_table);
! 	}
  
  	/*
  	 * Add necessary junk columns for rowmarked rels.  These values are needed
*************** preprocess_targetlist(PlannerInfo *root,
*** 96,102 ****
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  SelfItemPointerAttributeNumber,
  						  TIDOID,
  						  -1,
  						  InvalidOid,
--- 142,149 ----
  		{
  			/* It's a regular table, so fetch its TID */
  			var = makeVar(rc->rti,
! 						  lookup_varattno(SelfItemPointerAttributeNumber,
! 										  rc->rti, range_table),
  						  TIDOID,
  						  -1,
  						  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 112,118 ****
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  TableOidAttributeNumber,
  							  OIDOID,
  							  -1,
  							  InvalidOid,
--- 159,166 ----
  			if (rc->isParent)
  			{
  				var = makeVar(rc->rti,
! 							  lookup_varattno(TableOidAttributeNumber,
! 											  rc->rti, range_table),
  							  OIDOID,
  							  -1,
  							  InvalidOid,
*************** preprocess_targetlist(PlannerInfo *root,
*** 195,201 ****
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
--- 243,250 ----
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Index source_relation,
! 				  List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
*************** expand_targetlist(List *tlist, int comma
*** 218,223 ****
--- 267,275 ----
  
  	numattrs = RelationGetNumberOfAttributes(rel);
  
+ 	if (source_relation == 0)
+ 		source_relation = result_relation;
+ 
  	for (attrno = 1; attrno <= numattrs; attrno++)
  	{
  		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
*************** expand_targetlist(List *tlist, int comma
*** 298,305 ****
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(result_relation,
! 													attrno,
  													atttype,
  													atttypmod,
  													attcollation,
--- 350,359 ----
  				case CMD_UPDATE:
  					if (!att_tup->attisdropped)
  					{
! 						new_expr = (Node *) makeVar(source_relation,
! 													lookup_varattno(attrno,
! 														source_relation,
! 														range_table),
  													atttype,
  													atttypmod,
  													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index e249628..017bc2c
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** typedef struct
*** 55,60 ****
--- 55,61 ----
  {
  	PlannerInfo *root;
  	AppendRelInfo *appinfo;
+ 	bool		in_returning;
  } adjust_appendrel_attrs_context;
  
  static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1594,1599 ****
--- 1595,1601 ----
  
  	context.root = root;
  	context.appinfo = appinfo;
+ 	context.in_returning = false;
  
  	/*
  	 * Must be prepared to start with a Query or a bare expression tree.
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1605,1614 ****
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
--- 1607,1635 ----
  		newnode = query_tree_mutator((Query *) node,
  									 adjust_appendrel_attrs_mutator,
  									 (void *) &context,
! 									 QTW_IGNORE_RC_SUBQUERIES |
! 									 QTW_IGNORE_RETURNING);
! 		/*
! 		 * Returning clause on the relation being replaced with row-
! 		 * security subquery shall be handled in a special way, because
! 		 * of no system columns on subquery.
! 		 * Var references to system column or whole-row reference need
! 		 * to be adjusted to reference pseudo columns on behalf of
! 		 * the underlying these columns, however, RETURNGIN clause is
! 		 * an exception because its Var nodes are evaluated towards
! 		 * the "raw" target relation, not a fetched tuple.
! 		 */
! 		context.in_returning = true;
! 		newnode->returningList = (List *)
! 			expression_tree_mutator((Node *) newnode->returningList,
! 									adjust_appendrel_attrs_mutator,
! 									(void *) &context);
  		if (newnode->resultRelation == appinfo->parent_relid)
  		{
! 			newnode->resultRelation = (appinfo->child_result > 0 ?
! 									   appinfo->child_result :
! 									   appinfo->child_relid);
! 			newnode->sourceRelation = appinfo->child_relid;
  			/* Fix tlist resnos too, if it's inherited UPDATE */
  			if (newnode->commandType == CMD_UPDATE)
  				newnode->targetList =
*************** adjust_appendrel_attrs(PlannerInfo *root
*** 1624,1629 ****
--- 1645,1693 ----
  }
  
  static Node *
+ fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+ {
+ 	ListCell   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 	/*
+ 	 * In case when row-level security policy is applied on the referenced
+ 	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+ 	 * to filter out unprivileged rows of underlying relation.
+ 	 * Even though reference to this sub-query should perform as if ones
+ 	 * to real relations, system column has to be cared in special way
+ 	 * due to the nature of sub-query.
+ 	 * Target-entries that reference system columns should be added on
+ 	 * rowlevelsec.c, so all we need to do here is looking up underlying
+ 	 * target-list that can reference underlying system column, and fix-
+ 	 * up varattno of the referencing Var node with resno of TargetEntry.
+ 	 */
+ 	foreach (cell, rte->subquery->targetList)
+ 	{
+ 		TargetEntry *subtle = lfirst(cell);
+ 
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *) subtle->expr;
+ 			Var	   *newnode;
+ 
+ 			if (subvar->varattno == var->varattno)
+ 			{
+ 				newnode = copyObject(var);
+ 				newnode->varattno = subtle->resno;
+ 				return (Node *)newnode;
+ 			}
+ 		}
+ 		else
+ 			Assert(IsA(subtle->expr, Const));
+ 	}
+ 	elog(ERROR, "could not find pseudo column of %d, relid %u",
+ 		 var->varattno, var->varno);
+ 	return NULL;
+ }
+ 
+ static Node *
  adjust_appendrel_attrs_mutator(Node *node,
  							   adjust_appendrel_attrs_context *context)
  {
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1638,1645 ****
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = appinfo->child_relid;
  			var->varnoold = appinfo->child_relid;
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
--- 1702,1713 ----
  		if (var->varlevelsup == 0 &&
  			var->varno == appinfo->parent_relid)
  		{
! 			var->varno = (context->in_returning &&
! 						  appinfo->child_result > 0 ?
! 						  appinfo->child_result :
! 						  appinfo->child_relid);
  			var->varnoold = appinfo->child_relid;
+ 
  			if (var->varattno > 0)
  			{
  				Node	   *newnode;
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1664,1669 ****
--- 1732,1745 ----
  				 */
  				if (OidIsValid(appinfo->child_reltype))
  				{
+ 					Query          *parse = context->root->parse;
+ 					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+ 												   parse->rtable);
+ 					if (!context->in_returning &&
+ 						rte->rtekind == RTE_SUBQUERY &&
+ 						rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+ 
  					Assert(var->vartype == appinfo->parent_reltype);
  					if (appinfo->parent_reltype != appinfo->child_reltype)
  					{
*************** adjust_appendrel_attrs_mutator(Node *nod
*** 1708,1714 ****
  					return (Node *) rowexpr;
  				}
  			}
! 			/* system attributes don't need any other translation */
  		}
  		return (Node *) var;
  	}
--- 1784,1801 ----
  					return (Node *) rowexpr;
  				}
  			}
! 			else
! 			{
! 				Query		   *parse = context->root->parse;
! 				RangeTblEntry  *rte;
! 
! 				rte = rt_fetch(appinfo->child_relid, parse->rtable);
! 
! 				if (!context->in_returning &&
! 					rte->rtekind == RTE_SUBQUERY &&
! 					rte->subquery->querySource == QSRC_ROW_SECURITY)
! 					return fixup_var_on_rowsec_subquery(rte, var);
! 			}
  		}
  		return (Node *) var;
  	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
new file mode 100644
index 3b2d16b..3f5cb19
*** a/src/backend/optimizer/util/Makefile
--- b/src/backend/optimizer/util/Makefile
*************** top_builddir = ../../../..
*** 13,18 ****
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o
  
  include $(top_srcdir)/src/backend/common.mk
--- 13,18 ----
  include $(top_builddir)/src/Makefile.global
  
  OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
!        relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
  
  include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index ...c2e5a49
*** a/src/backend/optimizer/util/rowsecurity.c
--- b/src/backend/optimizer/util/rowsecurity.c
***************
*** 0 ****
--- 1,744 ----
+ /*
+  * optimizer/util/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/plannodes.h"
+ #include "optimizer/clauses.h"
+ #include "optimizer/prep.h"
+ #include "optimizer/rowsecurity.h"
+ #include "parser/parsetree.h"
+ #include "rewrite/rewriteHandler.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+ 
+ /* flags to pull row-security policy */
+ #define RSEC_FLAG_HAS_SUBLINKS			0x0001
+ 
+ /* hook to allow extensions to apply their own security policy */
+ row_security_policy_hook_type	row_security_policy_hook = NULL;
+ 
+ /*
+  * make_pseudo_column
+  *
+  * It makes a target-entry node that references underlying column.
+  * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+  * if the supplied attribute was already dropped.
+  */
+ static TargetEntry *
+ make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+ {
+ 	Expr   *expr;
+ 	char   *resname;
+ 
+ 	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+ 	if (attnum == InvalidAttrNumber)
+ 	{
+ 		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+ 		resname = get_rel_name(subrte->relid);
+ 	}
+ 	else
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(subrte->relid),
+ 								Int16GetDatum(attnum));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+ 				 attnum, subrte->relid);
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		if (attform->attisdropped)
+ 		{
+ 			char	namebuf[NAMEDATALEN];
+ 
+ 			/* Insert NULL just for a placeholder of dropped column */
+ 			expr = (Expr *) makeConst(INT4OID,
+ 									  -1,
+ 									  InvalidOid,
+ 									  sizeof(int32),
+ 									  (Datum) 0,
+ 									  true,		/* isnull */
+ 									  true);	/* byval */
+ 			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+ 			resname = pstrdup(namebuf);
+ 		}
+ 		else
+ 		{
+ 			expr = (Expr *) makeVar((Index) 1,
+ 									attform->attnum,
+ 									attform->atttypid,
+ 									attform->atttypmod,
+ 									attform->attcollation,
+ 									0);
+ 			resname = pstrdup(NameStr(attform->attname));
+ 		}
+ 		ReleaseSysCache(tuple);
+ 	}
+ 	return makeTargetEntry(expr, -1, resname, false);
+ }
+ 
+ /*
+  * lookup_pseudo_column
+  *
+  * It looks-up resource number of the target-entry relevant to the given
+  * Var-node that references the row-security subquery. If required column
+  * is not in the subquery's target-list, this function also adds new one
+  * and returns its resource number.
+  */
+ static AttrNumber
+ lookup_pseudo_column(PlannerInfo *root,
+ 					 RangeTblEntry *rte, AttrNumber varattno)
+ {
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	TargetEntry	   *subtle;
+ 	ListCell	   *cell;
+ 
+ 	Assert(rte->rtekind == RTE_SUBQUERY &&
+ 		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 	subqry = rte->subquery;
+ 	foreach (cell, subqry->targetList)
+ 	{
+ 		subtle = lfirst(cell);
+ 
+ 		/*
+ 		 * If referenced artifical column is already constructed on the
+ 		 * target-list of row-security subquery, nothing to do any more.
+ 		 */
+ 		if (IsA(subtle->expr, Var))
+ 		{
+ 			Var	   *subvar = (Var *)subtle->expr;
+ 
+ 			Assert(subvar->varno == 1);
+ 			if (subvar->varattno == varattno)
+ 				return subtle->resno;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * OK, we don't have an artifical column relevant to the required ones,
+ 	 * so let's create a new artifical column on demand.
+ 	 */
+ 	subrte = rt_fetch((Index) 1, subqry->rtable);
+ 	subtle = make_pseudo_column(subrte, varattno);
+ 	subtle->resno = list_length(subqry->targetList) + 1;
+ 
+ 	subqry->targetList = lappend(subqry->targetList, subtle);
+ 	rte->eref->colnames = lappend(rte->eref->colnames,
+ 								  makeString(pstrdup(subtle->resname)));
+ 	return subtle->resno;
+ }
+ 
+ /*
+  * fixup_varnode_walker
+  *
+  * It recursively fixes up references to the relation to be replaced by
+  * row-security sub-query, and adds pseudo columns relevant to the
+  * underlying system columns or whole row-reference on demand.
+  */
+ typedef struct {
+ 	PlannerInfo	*root;
+ 	int		varlevelsup;
+ 	Index  *vartrans;
+ } fixup_varnode_context;
+ 
+ static bool
+ fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 
+ 	if (IsA(node, Var))
+ 	{
+ 		Var			   *var = (Var *) node;
+ 		List		   *rtable = context->root->parse->rtable;
+ 		RangeTblEntry  *rte;
+ 		ListCell	   *cell;
+ 
+ 		/*
+ 		 * Ignore it, if Var node does not reference the Query currently
+ 		 * we focus on.
+ 		 */
+ 		if (var->varlevelsup != context->varlevelsup)
+ 			return false;
+ 
+ 		if (context->vartrans[var->varno] > 0)
+ 		{
+ 			Index	rtindex_trans = context->vartrans[var->varno];
+ 
+ 			rte = rt_fetch(rtindex_trans, rtable);
+ 			Assert(rte->rtekind == RTE_SUBQUERY &&
+ 				   rte->subquery->querySource == QSRC_ROW_SECURITY);
+ 
+ 			var->varno = var->varnoold = rtindex_trans;
+ 			var->varattno = lookup_pseudo_column(context->root, rte,
+ 													 var->varattno);
+ 		}
+ 		else
+ 		{
+ 			rte = rt_fetch(var->varno, rtable);
+ 			if (rte->rtekind == RTE_RELATION && rte->inh)
+ 			{
+ 				foreach (cell, context->root->append_rel_list)
+ 				{
+ 					AppendRelInfo  *appinfo = lfirst(cell);
+ 					RangeTblEntry  *child_rte;
+ 
+ 					if (appinfo->parent_relid != var->varno)
+ 						continue;
+ 
+ 					child_rte = rt_fetch(appinfo->child_relid, rtable);
+ 					if (child_rte->rtekind == RTE_SUBQUERY &&
+ 						child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+ 						(void) lookup_pseudo_column(context->root,
+ 													child_rte,
+ 													var->varattno);
+ 				}
+ 			}
+ 		}
+ 	}
+ 	else if (IsA(node, RangeTblRef))
+ 	{
+ 		RangeTblRef  *rtr = (RangeTblRef *) node;
+ 
+ 		if (context->varlevelsup == 0 &&
+ 			context->vartrans[rtr->rtindex] != 0)
+ 			rtr->rtindex = context->vartrans[rtr->rtindex];
+ 	}
+ 	else if (IsA(node, Query))
+ 	{
+ 		bool	result;
+ 
+ 		context->varlevelsup++;
+ 		result = query_tree_walker((Query *) node,
+ 								   fixup_varnode_walker,
+ 								   (void *) context, 0);
+ 		context->varlevelsup--;
+ 
+ 		return result;
+ 	}
+ 	return expression_tree_walker(node,
+ 								  fixup_varnode_walker,
+ 								  (void *) context);
+ }
+ 
+ /*
+  * check_infinite_recursion
+  *
+  * It is a wrong row-security configuration, if we try to expand
+  * the relation inside of row-security subquery originated from
+  * same relation!
+  */
+ static void
+ check_infinite_recursion(PlannerInfo *root, Oid relid)
+ {
+ 	PlannerInfo	   *parent = root->parent_root;
+ 
+ 	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+ 	{
+ 		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+ 
+ 		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+ 
+ 		if (relid == rte->relid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ 					 errmsg("infinite recursion detected for relation \"%s\"",
+ 							get_rel_name(relid))));
+ 		check_infinite_recursion(parent, relid);
+ 	}
+ }
+ 
+ /*
+  * expand_rtentry_with_policy
+  *
+  * It extends a range-table entry of row-security sub-query with supplied
+  * security policy, and append it on the parse->rtable.
+  * This sub-query contains pseudo columns that reference underlying
+  * regular columns (at least, references to system column or whole of
+  * table reference shall be added on demand), and simple scan on the
+  * target relation.
+  * Any Var nodes that referenced the relation pointed by rtindex shall
+  * be adjusted to reference this sub-query instead. walker
+  */
+ static Index
+ expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+ 						   Expr *qual, int flags)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Query		   *subqry;
+ 	RangeTblEntry  *subrte;
+ 	RangeTblRef	   *subrtr;
+ 	TargetEntry	   *subtle;
+ 	RangeTblEntry  *newrte;
+ 	HeapTuple		tuple;
+ 	AttrNumber		nattrs;
+ 	AttrNumber		attnum;
+ 	List		   *targetList = NIL;
+ 	List		   *colNameList = NIL;
+ 	PlanRowMark	   *rowmark;
+ 
+ 	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+ 
+ 	/* check recursion to prevent infinite loop */
+ 	check_infinite_recursion(root, rte->relid);
+ 
+ 	/* Expand views inside SubLink node */
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+ 
+ 	/*
+ 	 * Construction of sub-query
+ 	 */
+ 	subqry = (Query *) makeNode(Query);
+ 	subqry->commandType = CMD_SELECT;
+ 	subqry->querySource = QSRC_ROW_SECURITY;
+ 
+ 	subrte = copyObject(rte);
+ 	subqry->rtable = list_make1(subrte);
+ 
+ 	subrtr = makeNode(RangeTblRef);
+ 	subrtr->rtindex = 1;
+ 	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		subqry->hasSubLinks = true;
+ 
+ 	/*
+ 	 * Construction of TargetEntries that reference underlying columns.
+ 	 */
+ 	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+ 	if (!HeapTupleIsValid(tuple))
+ 		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+ 	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+ 	ReleaseSysCache(tuple);
+ 
+ 	for (attnum = 1; attnum <= nattrs; attnum++)
+ 	{
+ 		subtle = make_pseudo_column(subrte, attnum);
+ 		subtle->resno = list_length(targetList) + 1;
+ 		Assert(subtle->resno == attnum);
+ 
+ 		targetList = lappend(targetList, subtle);
+ 		colNameList = lappend(colNameList,
+ 							  makeString(pstrdup(subtle->resname)));
+ 	}
+ 	subqry->targetList = targetList;
+ 
+ 	/* Expand RengeTblEntry with this sub-query */
+ 	newrte = makeNode(RangeTblEntry);
+ 	newrte->rtekind = RTE_SUBQUERY;
+ 	newrte->subquery = subqry;
+ 	newrte->security_barrier = true;
+ 	newrte->rowsec_relid = rte->relid;
+ 	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+ 
+ 	parse->rtable = lappend(parse->rtable, newrte);
+ 
+ 	/*
+ 	 * Fix up PlanRowMark if needed, then add references to 'tableoid' and
+ 	 * 'ctid' that shall be added to handle row-level locking.
+ 	 * Also see preprocess_targetlist() that adds some junk attributes.
+ 	 */
+ 	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+ 	if (rowmark)
+ 	{
+ 		if (rowmark->rti == rowmark->prti)
+ 			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+ 		else
+ 			rowmark->rti = list_length(parse->rtable);
+ 
+ 		lookup_pseudo_column(root, newrte, SelfItemPointerAttributeNumber);
+ 		lookup_pseudo_column(root, newrte, TableOidAttributeNumber);
+ 	}
+ 	return list_length(parse->rtable);
+ }
+ 
+ /*
+  * pull_row_security_policy
+  *
+  * It pulls the configured row-security policy of both built-in and
+  * extensions. If any, it returns expression tree.
+  */
+ static Expr *
+ pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+ {
+ 	Expr   *quals = NULL;
+ 	int		flags = 0;
+ 
+ 	/*
+ 	 * Pull the row-security policy configured with built-in features,
+ 	 * if unprivileged users. Please note that superuser can bypass it.
+ 	 */
+ 	if (relation->rsdesc && !superuser())
+ 	{
+ 		RowSecurityDesc *rsdesc = relation->rsdesc;
+ 
+ 		quals = copyObject(rsdesc->rsall.qual);
+ 		if (rsdesc->rsall.hassublinks)
+ 			flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 	}
+ 
+ 	/*
+ 	 * Also, ask extensions whether they want to apply their own
+ 	 * row-security policy. If both built-in and extension has
+ 	 * their own policy, it shall be merged.
+ 	 */
+ 	if (row_security_policy_hook)
+ 	{
+ 		List   *temp;
+ 
+ 		temp = (*row_security_policy_hook)(cmd, relation);
+ 		if (temp != NIL)
+ 		{
+ 			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+ 				contain_subplans((Node *) temp))
+ 				flags |= RSEC_FLAG_HAS_SUBLINKS;
+ 
+ 			if (quals != NULL)
+ 				temp = lappend(temp, quals);
+ 
+ 			if (list_length(temp) == 1)
+ 				quals = (Expr *)list_head(temp);
+ 			else if (list_length(temp) > 1)
+ 				quals = makeBoolExpr(AND_EXPR, temp, -1);
+ 		}
+ 	}
+ 	*p_flags = flags;
+ 	return quals;
+ }
+ 
+ /*
+  * copy_row_security_policy
+  *
+  * It construct a row-security subquery instead of raw COPY TO statement,
+  * if target relation has a row-level security policy
+  */
+ bool
+ copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+ {
+ 	Expr		  *quals;
+ 	int			   flags;
+ 	Query		  *parse;
+ 	RangeTblEntry  *rte;
+ 	RangeTblRef	   *rtr;
+ 	TargetEntry	   *tle;
+ 	Var			   *var;
+ 	ListCell	   *cell;
+ 
+ 	if (stmt->is_from)
+ 		return false;
+ 
+ 	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+ 	if (!quals)
+ 		return false;
+ 
+ 	parse = (Query *) makeNode(Query);
+ 	parse->commandType = CMD_SELECT;
+ 	parse->querySource = QSRC_ROW_SECURITY;
+ 
+ 	rte = makeNode(RangeTblEntry);
+ 	rte->rtekind = RTE_RELATION;
+ 	rte->relid = RelationGetRelid(rel);
+ 	rte->relkind = RelationGetForm(rel)->relkind;
+ 
+ 	foreach (cell, attnums)
+ 	{
+ 		HeapTuple	tuple;
+ 		Form_pg_attribute	attform;
+ 		AttrNumber	attno = lfirst_int(cell);
+ 
+ 		tuple = SearchSysCache2(ATTNUM,
+ 								ObjectIdGetDatum(RelationGetRelid(rel)),
+ 								Int16GetDatum(attno));
+ 		if (!HeapTupleIsValid(tuple))
+ 			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+ 				 attno, RelationGetRelationName(rel));
+ 		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+ 
+ 		var = makeVar((Index) 1,
+ 					  attform->attnum,
+ 					  attform->atttypid,
+ 					  attform->atttypmod,
+ 					  attform->attcollation,
+ 					  0);
+ 		tle = makeTargetEntry((Expr *) var,
+ 							  list_length(parse->targetList) + 1,
+ 							  pstrdup(NameStr(attform->attname)),
+ 							  false);
+ 		parse->targetList = lappend(parse->targetList, tle);
+ 
+ 		ReleaseSysCache(tuple);
+ 
+ 		rte->selectedCols = bms_add_member(rte->selectedCols,
+ 								attno - FirstLowInvalidHeapAttributeNumber);
+ 	}
+ 	rte->inFromCl = true;
+ 	rte->requiredPerms = ACL_SELECT;
+ 
+ 	rtr = makeNode(RangeTblRef);
+ 	rtr->rtindex = 1;
+ 
+ 	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+ 	parse->rtable = list_make1(rte);
+ 	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+ 		parse->hasSubLinks = true;
+ 
+ 	stmt->query = (Node *) parse;
+ 
+ 	return true;
+ }
+ 
+ /*
+  * apply_row_security_relation
+  *
+  * It applies row-security policy on a particular relation being specified.
+  * If this relation is top of the inheritance tree, it also checks inherited
+  * children.
+  */
+ static bool
+ apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+ 							CmdType cmd, Index rtindex)
+ {
+ 	Query		   *parse = root->parse;
+ 	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 	Relation		rel;
+ 	Expr		   *qual;
+ 	int				flags;
+ 	bool			result = false;
+ 
+ 	if (!rte->inh)
+ 	{
+ 		rel = heap_open(rte->relid, NoLock);
+ 		qual = pull_row_security_policy(cmd, rel, &flags);
+ 		if (qual)
+ 		{
+ 			vartrans[rtindex]
+ 				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+ 			if (parse->resultRelation == rtindex)
+ 				parse->sourceRelation = vartrans[rtindex];
+ 			result = true;
+ 		}
+ 		heap_close(rel, NoLock);
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * In case when relation has inherited children, we try to apply
+ 		 * row-level security policy of them if configured.
+ 		 * In addition to regular replacement with a sub-query, we need
+ 		 * to adjust rtindex of AppendRelInfo and varno of translated_vars.
+ 		 * It makes sub-queries perform like regular relations being
+ 		 * inherited from a particular parent relation. So, a table scan
+ 		 * may have underlying a relation scan and two sub-query scans for
+ 		 * instance. If it is result relation of UPDATE or DELETE command,
+ 		 * rtindex to the original relation (regular relation) has to be
+ 		 * kept because sub-query cannot perform as an updatable relation.
+ 		 * So, we save it on child_result of AppendRelInfo; that shall be
+ 		 * used to track relations to be modified at inheritance_planner().
+ 		 */
+ 		ListCell   *lc1, *lc2;
+ 
+ 		foreach (lc1, root->append_rel_list)
+ 		{
+ 			AppendRelInfo  *apinfo = lfirst(lc1);
+ 
+ 			if (apinfo->parent_relid != rtindex)
+ 				continue;
+ 
+ 			if (apply_row_security_relation(root, vartrans, cmd,
+ 											apinfo->child_relid))
+ 			{
+ 				/*
+ 				 * Save the rtindex of actual relation to be modified,
+ 				 * if parent relation is result relation of this query.
+ 				 */
+ 				if (parse->resultRelation == rtindex)
+ 					apinfo->child_result = apinfo->child_relid;
+ 
+ 				apinfo->child_relid = vartrans[apinfo->child_relid];
+ 				/* Adjust varno to reference pseudo columns */
+ 				foreach (lc2, apinfo->translated_vars)
+ 				{
+ 					Var	   *var = lfirst(lc2);
+ 
+ 					if (var)
+ 						var->varno = apinfo->child_relid;
+ 				}
+ 				result = true;
+ 			}
+ 		}
+ 	}
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_recursive
+  *
+  * It walks on the given join-tree to replace relations with row-level
+  * security policy by a simple sub-query.
+  */
+ static bool
+ apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+ {
+ 	bool	result = false;
+ 
+ 	if (jtnode == NULL)
+ 		return false;
+ 	if (IsA(jtnode, RangeTblRef))
+ 	{
+ 		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+ 		Query		   *parse = root->parse;
+ 		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+ 		CmdType			cmd;
+ 
+ 		/* Only relation can have row-security policy */
+ 		if (rte->rtekind != RTE_RELATION)
+ 			return false;
+ 
+ 		/*
+ 		 * Prevents infinite recursion. Please note that rtindex == 1
+ 		 * of the row-security subquery is a relation being already
+ 		 * processed on the upper level.
+ 		 */
+ 		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+ 			return false;
+ 
+ 		/* Is it a result relation of UPDATE or DELETE command? */
+ 		if (parse->resultRelation == rtindex)
+ 			cmd = parse->commandType;
+ 		else
+ 			cmd = CMD_SELECT;
+ 
+ 		/* Try to apply row-security policy, if configured */
+ 		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+ 	}
+ 	else if (IsA(jtnode, FromExpr))
+ 	{
+ 		FromExpr   *f = (FromExpr *) jtnode;
+ 		ListCell   *l;
+ 
+ 		foreach (l, f->fromlist)
+ 		{
+ 			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+ 				result = true;
+ 		}
+ 	}
+ 	else if (IsA(jtnode, JoinExpr))
+ 	{
+ 		JoinExpr   *j = (JoinExpr *) jtnode;
+ 
+ 		if (apply_row_security_recursive(root, vartrans, j->larg))
+ 			result = true;
+ 		if (apply_row_security_recursive(root, vartrans, j->rarg))
+ 			result = true;
+ 	}
+ 	else
+ 		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+ 
+ 	return result;
+ }
+ 
+ /*
+  * apply_row_security_policy
+  *
+  * Entrypoint to apply configured row-security policy of the relation.
+  *
+  * In case when the supplied query references relations with row-security
+  * policy, its RangeTblEntry shall be replaced by a row-security subquery
+  * that has simple scan on the referenced table with policy qualifiers.
+  * Of course, security-barrier shall be set on the subquery to prevent
+  * unexpected push-down of functions without leakproof flag.
+  *
+  * For example, when table t1 has a security policy "(x % 2 = 0)", the
+  * following query:
+  *   SELECT * FROM t1 WHERE f_leak(y)
+  * performs as if
+  *   SELECT * FROM (
+  *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+  *   ) AS t1 WHERE f_leak(y)
+  * would be given. Because the sub-query has security barrier flag, 
+  * configured security policy qualifier is always executed prior to
+  * user given functions.
+  */
+ void
+ apply_row_security_policy(PlannerInfo *root)
+ {
+ 	Query	   *parse = root->parse;
+ 	Oid			curr_userid;
+ 	int			curr_seccxt;
+ 	Index	   *vartrans;
+ 
+ 	/*
+ 	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+ 	 * no row-level security policy should be applied regardless
+ 	 * whether it is built-in or extension.
+ 	 */
+ 	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+ 	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+ 		return;
+ 
+ 	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+ 	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+ 	{
+ 		PlannerGlobal  *glob = root->glob;
+ 		PlanInvalItem  *pi_item;
+ 		fixup_varnode_context context;
+ 
+ 		/*
+ 		 * Constructed Plan with row-level security policy depends on
+ 		 * properties of current user (database superuser can bypass
+ 		 * configured row-security policy!), thus, it has to be
+ 		 * invalidated when its assumption was changed.
+ 		 */
+ 		if (!OidIsValid(glob->planUserId))
+ 		{
+ 			/* Plan invalidation on session user-id */
+ 			glob->planUserId = GetUserId();
+ 
+ 			/* Plan invalidation on catalog updates of pg_authid */
+ 			pi_item = makeNode(PlanInvalItem);
+ 			pi_item->cacheId = AUTHOID;
+ 			pi_item->hashValue =
+ 				GetSysCacheHashValue1(AUTHOID,
+ 									  ObjectIdGetDatum(glob->planUserId));
+ 			glob->invalItems = lappend(glob->invalItems, pi_item);
+ 		}
+ 		else
+ 			Assert(glob->planUserId == GetUserId());
+ 
+ 		/*
+ 		 * Var-nodes that referenced RangeTblEntry to be replaced by
+ 		 * row-security sub-query have to be adjusted for appropriate
+ 		 * reference to the underlying pseudo column of the relation.
+ 		 */
+ 		context.root = root;
+ 		context.varlevelsup = 0;
+ 		context.vartrans = vartrans;
+ 		query_tree_walker(parse,
+ 						  fixup_varnode_walker,
+ 						  (void *) &context,
+ 						  QTW_IGNORE_RETURNING);
+ 	}
+ 	pfree(vartrans);
+ }
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index 1922097..2c22de8
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static Node *makeRecursiveViewSelect(cha
*** 257,262 ****
--- 257,263 ----
  %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
  	   replica_identity
  %type <list>	alter_table_cmds alter_type_cmds
+ %type <str>		row_security_cmd
  
  %type <dbehavior>	opt_drop_behavior
  
*************** alter_table_cmd:
*** 2183,2188 ****
--- 2184,2206 ----
  					n->def = (Node *)$2;
  					$$ = (Node *)n;
  				}
+ 			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+ 			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_SetRowSecurity;
+ 					n->name = $5;
+ 					n->def = (Node *) $8;
+ 					$$ = (Node *)n;
+ 				}
+ 			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+ 			| RESET ROW SECURITY FOR row_security_cmd
+ 				{
+ 					AlterTableCmd *n = makeNode(AlterTableCmd);
+ 					n->subtype = AT_ResetRowSecurity;
+ 					n->name = $5;
+ 					n->def = NULL;
+ 				}
  			/* ALTER TABLE <name> REPLICA IDENTITY  */
  			| REPLICA IDENTITY_P replica_identity
  				{
*************** reloption_elem:
*** 2293,2298 ****
--- 2311,2322 ----
  				}
  		;
  
+ row_security_cmd: ALL	{ $$ = "all"; }
+ 			| SELECT	{ $$ = "select"; }
+ 			| INSERT	{ $$ = "insert"; }
+ 			| UPDATE	{ $$ = "update"; }
+ 			| DELETE_P	{ $$ = "delete"; }
+ 		;
  
  /*****************************************************************************
   *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
new file mode 100644
index 98cb58a..ac50a2e
*** a/src/backend/parser/parse_agg.c
--- b/src/backend/parser/parse_agg.c
*************** transformAggregateCall(ParseState *pstat
*** 272,277 ****
--- 272,280 ----
  		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
*** 547,552 ****
--- 550,558 ----
  		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 68b711d..fb09c8c
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
*************** transformSubLink(ParseState *pstate, Sub
*** 1459,1464 ****
--- 1459,1465 ----
  		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
*** 2640,2645 ****
--- 2641,2648 ----
  			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/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index 50cb753..f549293
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** QueryRewrite(Query *parsetree)
*** 3282,3284 ****
--- 3282,3300 ----
  
  	return results;
  }
+ 
+ /*
+  * QueryRewriteExpr
+  *
+  * This routine provides an entry point of query rewriter towards
+  * a certain expression tree with SubLink node; being added after
+  * the top level query rewrite.
+  * It primarily intends to expand views appeared in the qualifiers
+  * appended with row-level security which needs to modify query
+  * tree at head of the planner stage.
+  */
+ void
+ QueryRewriteExpr(Node *node, List *activeRIRs)
+ {
+ 	fireRIRonSubLink(node, activeRIRs);
+ }
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 917130f..b24af71
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3008,3013 ****
--- 3008,3014 ----
  	int			spi_result;
  	Oid			save_userid;
  	int			save_sec_context;
+ 	int			temp_sec_context;
  	Datum		vals[RI_MAX_NUMKEYS * 2];
  	char		nulls[RI_MAX_NUMKEYS * 2];
  
*************** ri_PerformCheck(const RI_ConstraintInfo 
*** 3087,3094 ****
  
  	/* 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);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
--- 3088,3105 ----
  
  	/* Switch to proper UID to perform check as */
  	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+ 
+ 	/*
+ 	 * Row-level security should be disabled in case when 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 (source_is_pk)
+ 		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+ 
  	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
! 						   temp_sec_context);
  
  	/* Finally we can run the query. */
  	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
new file mode 100644
index cf740a9..f1e67a5
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "catalog/namespace.h"
  #include "executor/executor.h"
  #include "executor/spi.h"
+ #include "miscadmin.h"
  #include "nodes/nodeFuncs.h"
  #include "optimizer/cost.h"
  #include "optimizer/planmain.h"
*************** CheckCachedPlan(CachedPlanSource *planso
*** 795,800 ****
--- 796,811 ----
  		AcquireExecutorLocks(plan->stmt_list, true);
  
  		/*
+ 		 * If plan was constructed with assumption of a particular user-id,
+ 		 * and it is different from the current one, the cached-plan shall
+ 		 * be invalidated to construct suitable query plan.
+ 		 */
+ 		if (plan->is_valid &&
+ 			OidIsValid(plan->planUserId) &&
+ 			plan->planUserId == GetUserId())
+ 			plan->is_valid = false;
+ 
+ 		/*
  		 * If plan was transient, check to see if TransactionXmin has
  		 * advanced, and if so invalidate it.
  		 */
*************** BuildCachedPlan(CachedPlanSource *planso
*** 847,852 ****
--- 858,865 ----
  {
  	CachedPlan *plan;
  	List	   *plist;
+ 	ListCell   *cell;
+ 	Oid			planUserId = InvalidOid;
  	bool		snapshot_set;
  	bool		spi_pushed;
  	MemoryContext plan_context;
*************** BuildCachedPlan(CachedPlanSource *planso
*** 914,919 ****
--- 927,950 ----
  		PopActiveSnapshot();
  
  	/*
+ 	 * Check whether the generated plan assumes a particular user-id, or not.
+ 	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+ 	 * it should be kept and used to validation check of the cached plan
+ 	 * under the "current" user-id.
+ 	 */
+ 	foreach (cell, plist)
+ 	{
+ 		PlannedStmt	*pstmt = lfirst(cell);
+ 
+ 		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+ 		{
+ 			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+ 
+ 			planUserId = pstmt->planUserId;
+ 		}
+ 	}
+ 
+ 	/*
  	 * Normally we make a dedicated memory context for the CachedPlan and its
  	 * subsidiary data.  (It's probably not going to be large, but just in
  	 * case, use the default maxsize parameter.  It's transient for the
*************** BuildCachedPlan(CachedPlanSource *planso
*** 956,961 ****
--- 987,993 ----
  	plan->is_oneshot = plansource->is_oneshot;
  	plan->is_saved = false;
  	plan->is_valid = true;
+ 	plan->planUserId = planUserId;
  
  	/* assign generation number to new plan */
  	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
new file mode 100644
index d0acca8..2137e98
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 50,55 ****
--- 50,56 ----
  #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"
*************** RelationBuildDesc(Oid targetRelId, bool 
*** 932,937 ****
--- 933,943 ----
  	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
*** 1840,1845 ****
--- 1846,1853 ----
  		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)
*** 3166,3172 ****
  				relation->rd_rel->relhastriggers = false;
  			restart = true;
  		}
! 
  		/* Release hold on the relation */
  		RelationDecrementReferenceCount(relation);
  
--- 3174,3186 ----
  				relation->rd_rel->relhastriggers = false;
  			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)
*** 4443,4448 ****
--- 4457,4463 ----
  		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
new file mode 100644
index 247ad92..9603ef4
*** 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 63a8009..25ff339
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
*************** _printTocEntry(ArchiveHandle *AH, TocEnt
*** 3133,3138 ****
--- 3133,3139 ----
  				 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 224e8cb..7d60cf1
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
*************** static char *myFormatType(const char *ty
*** 250,255 ****
--- 250,256 ----
  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);
*************** dumpBlobs(Archive *fout, void *arg)
*** 2714,2719 ****
--- 2715,2848 ----
  	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_rseccmd;
+ 	int			i_rsecqual;
+ 	int			i, j, ntups;
+ 
+ 	/* row-security is not supported prior to v9.4 */
+ 	if (fout->remoteVersion < 90400)
+ 		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.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_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].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;
+ 
+ 	query = createPQExpBuffer();
+ 	delqry = createPQExpBuffer();
+ 	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+ 					  fmtId(tbinfo->dobj.name));
+ 	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);
+ 	}
+ 	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+ 	appendPQExpBuffer(delqry, "FOR %s;\n", 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)
*** 4242,4247 ****
--- 4371,4377 ----
  	int			i_relhastriggers;
  	int			i_relhasindex;
  	int			i_relhasrules;
+ 	int			i_relhasrowsec;
  	int			i_relhasoids;
  	int			i_relfrozenxid;
  	int			i_toastoid;
*************** getTables(Archive *fout, int *numTables)
*** 4293,4302 ****
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
! 						  "c.relfrozenxid, tc.oid AS toid, "
! 						  "tc.relfrozenxid AS tfrozenxid, "
! 						  "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, "
--- 4423,4429 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
! 						  "c.relhasrowsecurity, "
  						  "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, "
*************** getTables(Archive *fout, int *numTables)
*** 4332,4337 ****
--- 4459,4465 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, c.relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4371,4376 ****
--- 4499,4505 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "c.relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4408,4413 ****
--- 4537,4543 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4444,4449 ****
--- 4574,4580 ----
  						  "(%s c.relowner) AS rolname, "
  						  "c.relchecks, c.relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4480,4485 ****
--- 4611,4617 ----
  						  "(%s c.relowner) AS rolname, "
  					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
  						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "c.relfrozenxid, tc.oid AS toid, "
  						  "tc.relfrozenxid AS tfrozenxid, "
  						  "'p' AS relpersistence, 't' as relispopulated, "
*************** getTables(Archive *fout, int *numTables)
*** 4516,4521 ****
--- 4648,4654 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4552,4557 ****
--- 4685,4691 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4584,4589 ****
--- 4718,4724 ----
  						  "(%s relowner) AS rolname, "
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4611,4616 ****
--- 4746,4752 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 AS relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4648,4653 ****
--- 4784,4790 ----
  						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
  						  "relhasindex, relhasrules, "
  						  "'t'::bool AS relhasoids, "
+ 						  "'f'::bool AS relhasrowsecurity, "
  						  "0 as relfrozenxid, "
  						  "0 AS toid, "
  						  "0 AS tfrozenxid, "
*************** getTables(Archive *fout, int *numTables)
*** 4695,4700 ****
--- 4832,4838 ----
  	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_toastoid = PQfnumber(res, "toid");
*************** getTables(Archive *fout, int *numTables)
*** 4744,4749 ****
--- 4882,4888 ----
  		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
*** 7861,7866 ****
--- 8000,8008 ----
  						 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 *
*** 15118,15123 ****
--- 15260,15266 ----
  			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 915e82c..72c0433
*** 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 */
  	Oid			toast_oid;		/* for restore toast frozen xid */
*************** typedef struct _blobInfo
*** 484,489 ****
--- 486,499 ----
  	char	   *blobacl;
  } BlobInfo;
  
+ typedef struct _rowSecurityInfo
+ {
+ 	DumpableObject dobj;
+ 	TableInfo  *rstable;
+ 	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
*** 575,579 ****
--- 585,590 ----
  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 141e713..5c5777c
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
*************** describeDumpableObject(DumpableObject *o
*** 1342,1347 ****
--- 1342,1352 ----
  					 "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/psql/describe.c b/src/bin/psql/describe.c
new file mode 100644
index 96322ca..89e1c09
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
*************** listTables(const char *tabtypes, const c
*** 2752,2757 ****
--- 2752,2761 ----
  		appendPQExpBuffer(&buf,
  			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
  						  gettext_noop("Description"));
+ 		if (pset.sversion >= 90300)
+ 			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
*** 2761,2766 ****
--- 2765,2773 ----
  		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 >= 90300)
+ 		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 3aefbb5..c96e8d4
*** 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 4860e98..04dbeca
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
*************** DECLARE_UNIQUE_INDEX(pg_extension_name_i
*** 313,318 ****
--- 313,323 ----
  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(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_class.h b/src/include/catalog/pg_class.h
new file mode 100644
index a1fee11..d937924
*** 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,153 ----
   * 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 29 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 ...798d556
*** a/src/include/catalog/pg_rowsecurity.h
--- b/src/include/catalog/pg_rowsecurity.h
***************
*** 0 ****
--- 1,76 ----
+ /*
+  * 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_rowlevelsec definition. cpp turns this into
+  *		typedef struct FormData_pg_rowlevelsec
+  * ----------------
+  */
+ #define RowSecurityRelationId	5000
+ 
+ CATALOG(pg_rowsecurity,5000)
+ {
+ 	/* Oid of the relation that has row-security policy */
+ 	Oid				rsecrelid;
+ 
+ 	/* One of ROWSECURITY_CMD_* below */
+ 	char			rseccmd;
+ #ifdef CATALOG_VARLEN
+ 	pg_node_tree	rsecqual;
+ #endif
+ } FormData_pg_rowsecurity;
+ 
+ /* ----------------
+  *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+  *		the format of pg_rowlevelsec relation.
+  * ----------------
+  */
+ typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+ 
+ /* ----------------
+  * 		compiler constants for pg_rowlevelsec
+  * ----------------
+  */
+ #define Natts_pg_rowsecurity				3
+ #define Anum_pg_rowsecurity_rsecrelid		1
+ #define Anum_pg_rowsecurity_rseccmd			2
+ #define Anum_pg_rowsecurity_rsecqual		3
+ 
+ #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
+ {
+ 	MemoryContext		rscxt;
+ 	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+ } RowSecurityDesc;
+ 
+ extern void	RelationBuildRowSecurity(Relation relation);
+ extern void	ATExecSetRowSecurity(Relation relation,
+ 								 const char *cmdname, Node *clause);
+ extern void	RemoveRowSecurityById(Oid relationId);
+ 
+ #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
new file mode 100644
index 98ca553..8d76387
*** 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/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index 5a40347..4133b8c
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct JunkFilter
*** 308,313 ****
--- 308,315 ----
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
   *		projectReturning		for computing a RETURNING list
+  *		rowSecurity				for row-security checks
+  *		rowSecParams			param-list if row-security has SubLink
   * ----------------
   */
  typedef struct ResultRelInfo
*************** typedef struct ResultRelInfo
*** 329,334 ****
--- 331,338 ----
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
  	ProjectionInfo *ri_projectReturning;
+ 	Node	   *ri_rowSecurity;
+ 	List	   *ri_rowSecParams;
  } ResultRelInfo;
  
  /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
new file mode 100644
index fe7cfd3..23c3553
*** a/src/include/nodes/nodeFuncs.h
--- b/src/include/nodes/nodeFuncs.h
***************
*** 24,29 ****
--- 24,30 ----
  #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
  #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
  #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+ #define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
  
  
  extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 6a5555f..8163708
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum QuerySource
*** 31,37 ****
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
--- 31,38 ----
  	QSRC_PARSER,				/* added by parse analysis (now unused) */
  	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
  	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
! 	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
! 	QSRC_ROW_SECURITY,			/* added by row-security */
  } QuerySource;
  
  /* Sort ordering options for ORDER BY and CREATE INDEX */
*************** typedef struct Query
*** 112,118 ****
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
--- 113,121 ----
  
  	int			resultRelation; /* rtable index of target relation for
  								 * INSERT/UPDATE/DELETE; 0 for SELECT */
! 	int			sourceRelation;	/* rtable index of source relation for
! 								 * UPDATE/DELETE, if not identical with
! 								 * resultRelation; 0 for elsewhere */
  	bool		hasAggs;		/* has aggregates in tlist or havingQual */
  	bool		hasWindowFuncs; /* has window functions in tlist */
  	bool		hasSubLinks;	/* has subquery SubLink */
*************** typedef struct RangeTblEntry
*** 739,744 ****
--- 742,752 ----
  	 */
  	Query	   *subquery;		/* the sub-query */
  	bool		security_barrier;		/* is from security_barrier view? */
+ 	Oid			rowsec_relid;	/* OID of the original relation, if this
+ 								 * sub-query originated from row-security
+ 								 * policy on the relation. Elsewhere, it
+ 								 * should be InvalidOid.
+ 								 */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
*************** typedef enum AlterTableType
*** 1314,1319 ****
--- 1322,1329 ----
  	AT_AddOf,					/* OF <type_name> */
  	AT_DropOf,					/* NOT OF */
  	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+ 	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+ 	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
  	AT_GenericOptions			/* OPTIONS (...) */
  } AlterTableType;
  
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 101e22c..e675e87
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct PlannedStmt
*** 67,72 ****
--- 67,74 ----
  	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
  
  	int			nParamExec;		/* number of PARAM_EXEC Params used */
+ 
+ 	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
new file mode 100644
index 6d7b594..ee454fa
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerGlobal
*** 98,103 ****
--- 98,105 ----
  	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
  
  	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+ 
+ 	Oid			planUserId;		/* User-Id to be assumed on this plan */
  } PlannerGlobal;
  
  /* macro for fetching the Plan associated with a SubPlan node */
*************** typedef struct AppendRelInfo
*** 1435,1440 ****
--- 1437,1446 ----
  	 */
  	Index		parent_relid;	/* RT index of append parent rel */
  	Index		child_relid;	/* RT index of append child rel */
+ 	Index		child_result;	/* RT index of append child rel's source,
+ 								 * if source of result relation is not
+ 								 * identical. Elsewhere, 0.
+ 								 */
  
  	/*
  	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index ...ff4dd9a
*** a/src/include/optimizer/rowsecurity.h
--- b/src/include/optimizer/rowsecurity.h
***************
*** 0 ****
--- 1,27 ----
+ /* -------------------------------------------------------------------------
+  *
+  * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+ 									 Relation relation, List *attnums);
+ extern void	apply_row_security_policy(PlannerInfo *root);
+ 
+ #endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
new file mode 100644
index bea3b07..3910853
*** 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/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
new file mode 100644
index c959590..ef10f56
*** a/src/include/rewrite/rewriteHandler.h
--- b/src/include/rewrite/rewriteHandler.h
***************
*** 18,23 ****
--- 18,24 ----
  #include "nodes/parsenodes.h"
  
  extern List *QueryRewrite(Query *parsetree);
+ extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
  extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
  
  extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
new file mode 100644
index 72f8491..35819fa
*** a/src/include/utils/plancache.h
--- b/src/include/utils/plancache.h
*************** typedef struct CachedPlan
*** 128,133 ****
--- 128,135 ----
  	bool		is_oneshot;		/* is it a "oneshot" plan? */
  	bool		is_saved;		/* is CachedPlan in a long-lived context? */
  	bool		is_valid;		/* is the stmt_list currently valid? */
+ 	Oid			planUserId;		/* is user-id that is assumed on this cached
+ 								   plan, or InvalidOid if portable for anybody */
  	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
  								 * changes from this value */
  	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
new file mode 100644
index 21d5871..9a324ab
*** 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
*** 109,114 ****
--- 110,116 ----
  	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 */
  
  	/*
  	 * The index chosen as the relation's replication identity or
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index ...04591df
*** a/src/test/regress/expected/rowsecurity.out
--- b/src/test/regress/expected/rowsecurity.out
***************
*** 0 ****
--- 1,950 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ 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);
+ 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
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+ (4 rows)
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+   11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+   22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+   22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+   33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+   44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+   44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+   44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ERROR:  must be owner of relation document
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ ERROR:  must be owner of relation document
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ 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);
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 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'); -- failed
+ ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+ DETAIL:  Key (cid)=(33) is not present in table "category".
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ 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;
+ 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);
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+ SELECT * FROM t1;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ SELECT * FROM t1 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          Filter: f_leak(t1.b)
+          ->  Seq Scan on t1 t1_1
+                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)
+ 
+ -- reference to system column
+ SELECT oid, * FROM t1;
+  oid | a |  b  
+ -----+---+-----
+  102 | 2 | bbb
+  104 | 4 | ddd
+  201 | 1 | abc
+  203 | 3 | cde
+  301 | 1 | xxx
+  302 | 2 | yyy
+  303 | 3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- reference to whole-row reference
+ SELECT *,t1 FROM t1;
+  a |  b  |   t1    
+ ---+-----+---------
+  2 | bbb | (2,bbb)
+  4 | ddd | (4,ddd)
+  1 | abc | (1,abc)
+  3 | cde | (3,cde)
+  1 | xxx | (1,xxx)
+  2 | yyy | (2,yyy)
+  3 | zzz | (3,zzz)
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT *,t1 FROM t1;
+              QUERY PLAN              
+ -------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a % 2) = 0)
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a % 2) = 1)
+    ->  Seq Scan on t3
+ (8 rows)
+ 
+ -- for share/update lock
+ SELECT * FROM t1 FOR SHARE;
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                ->  Seq Scan on t1 t1_1
+                      Filter: ((a % 2) = 0)
+          ->  Subquery Scan on t2
+                ->  Seq Scan on t2 t2_1
+                      Filter: ((a % 2) = 1)
+          ->  Seq Scan on t3
+ (9 rows)
+ 
+ SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+ 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
+  a |  b  
+ ---+-----
+  2 | bbb
+  4 | ddd
+  1 | abc
+  3 | cde
+  1 | xxx
+  2 | yyy
+  3 | zzz
+ (7 rows)
+ 
+ EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                 QUERY PLAN                 
+ -------------------------------------------
+  LockRows
+    ->  Append
+          ->  Subquery Scan on t1
+                Filter: f_leak(t1.b)
+                ->  Seq Scan on t1 t1_1
+                      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)
+ (12 rows)
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ 2	bbb
+ 4	ddd
+ COPY t1 TO stdout WITH OIDS;
+ 102	2	bbb
+ 104	4	ddd
+ COPY t2(c,b) TO stdout WITH OIDS;
+ 201	1.1	abc
+ 203	3.3	cde
+ COPY (SELECT * FROM t1) TO stdout;
+ 2	bbb
+ 4	ddd
+ 1	abc
+ 3	cde
+ 1	xxx
+ 2	yyy
+ 3	zzz
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ ERROR:  table "document" does not have OIDs
+ --
+ -- 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%';
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ ERROR:  infinite recursion detected for relation "s1"
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                      ->  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)
+ (11 rows)
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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
+                      ->  Subquery Scan on s2
+                            Filter: (s2.y ~~ '%af%'::text)
+                            ->  Seq Scan on s2 s2_1
+                                  Filter: ((x % 2) = 0)
+ (11 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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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 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
+  1 | abc
+  1 | xxx
+  2 | yyy
+ (4 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p1(2);
+                      QUERY PLAN                     
+ ----------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a <= 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a <= 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a <= 2)
+ (9 rows)
+ 
+ -- superuser is allowed to bypass RLS checks
+ RESET SESSION AUTHORIZATION;
+ 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;
+ EXECUTE p2(2);
+  a |  b  
+ ---+-----
+  2 | bbb
+  2 | yyy
+ (2 rows)
+ 
+ EXPLAIN (costs off) EXECUTE p2(2);
+                     QUERY PLAN                     
+ ---------------------------------------------------
+  Append
+    ->  Subquery Scan on t1
+          ->  Seq Scan on t1 t1_1
+                Filter: ((a = 2) AND ((a % 2) = 0))
+    ->  Subquery Scan on t2
+          ->  Seq Scan on t2 t2_1
+                Filter: ((a = 2) AND ((a % 2) = 1))
+    ->  Seq Scan on t3
+          Filter: (a = 2)
+ (9 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
+    ->  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)
+ 
+ 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
+    ->  Subquery Scan on t1_1
+          Filter: f_leak(t1_1.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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+  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 |             | 
+ (8 rows)
+ 
+ --
+ -- Clean up objects
+ --
+ RESET SESSION AUTHORIZATION;
+ DROP SCHEMA rls_regress_schema CASCADE;
+ NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
new file mode 100644
index a62a3e3..a41344b
*** 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 5758b07..f7963fc
*** 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 rowsecurity security_label collate matview lock replica_identity
  
  # ----------
  # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
new file mode 100644
index 78348f5..51c4c12
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
*************** test: delete
*** 94,99 ****
--- 94,100 ----
  test: namespace
  test: prepared_xacts
  test: privileges
+ test: rowsecurity
  test: security_label
  test: collate
  test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index ...55d4aad
*** a/src/test/regress/sql/rowsecurity.sql
--- b/src/test/regress/sql/rowsecurity.sql
***************
*** 0 ****
--- 1,298 ----
+ --
+ -- 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 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 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
+ );
+ INSERT INTO uaccount VALUES
+     ('rls_regress_user0', 99),
+     ('rls_regress_user1',  1),
+     ('rls_regress_user2',  2),
+     ('rls_regress_user3',  3);
+ GRANT SELECT ON uaccount TO public;
+ 
+ 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 higher than or equal to document's one
+ ALTER TABLE document SET ROW SECURITY FOR ALL
+     TO (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);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ 
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+ 
+ -- viewpoint from rls_regress_user1 again
+ SET SESSION AUTHORIZATION rls_regress_user1;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ -- viewpoint from rls_regress_user2 again
+ SET SESSION AUTHORIZATION rls_regress_user2;
+ SELECT * FROM document WHERE f_leak(dtitle);
+ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+ 
+ 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;
+ ALTER TABLE category SET ROW SECURITY FOR ALL
+     TO (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;    -- failed
+ 
+ -- 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'); -- failed
+ 
+ -- database superuser can bypass RLS policy
+ RESET SESSION AUTHORIZATION;
+ SELECT * FROM document;
+ SELECT * FROM category;
+ 
+ --
+ -- Table inheritance and RLS policy
+ --
+ SET SESSION AUTHORIZATION rls_regress_user0;
+ 
+ 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);
+ 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
+ \.
+ 
+ ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+ 
+ --
+ -- COPY TO statement 
+ --
+ COPY t1 TO stdout;
+ COPY t1 TO stdout WITH OIDS;
+ COPY t2(c,b) TO stdout WITH OIDS;
+ COPY (SELECT * FROM t1) TO stdout;
+ COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ 
+ --
+ -- 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%';
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (a in (select x from s2 where y like '%2f%'));
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL
+    TO (x in (select a from s1 where b like '%22%'));
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ 
+ ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+ 
+ SELECT * FROM s1 WHERE f_leak(b);	-- OK
+ EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+ 
+ ALTER TABLE s1 SET ROW SECURITY FOR ALL
+    TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+    TO (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;
+ 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;
+ 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
+ --
+ ALTER TABLE category RESET ROW SECURITY 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;

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
@ 2013-12-14 04:40                                                                       ` Craig Ringer <craig@2ndquadrant.com>
  2013-12-18 08:41                                                                         ` Re: [v9.4] row level security Yeb Havinga <yebhavinga@gmail.com>
  2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Craig Ringer @ 2013-12-14 04:40 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

On 12/14/2013 11:24 AM, Gregory Smith wrote:

>  The RLS feature set available with the CF submission is good enough
> for those projects to continue exploring PostgreSQL

You may want to check out the updated writable security-barrier views patch.

http://www.postgresql.org/message-id/52AB112B.6020403@2ndquadrant.com

It may offer a path forward for the CF submission for RLS, letting us
get rid of the var/attr fiddling that many here objected to.

> There's some interesting bad
> news, like how we're going to have to completely refactor all the "if
> (!superuser())" shortcuts into real permissions one day to make them
> happy.

We'll need that for mandatory access control. Initially I'd probably
want to do it through SEPostgreSQL role checks, but it'd be desirable to
have a generic mechanism that'd work with any OS's role management
eventually.

I think we're going to need finer grained rights than we have now, as
"superuser or not" isn't really good enough for some things. In
particular, the fact that if plperl or plpython are installed the
superuser (often accessible remotely) can trivially run arbitrary
C-level functions and invoke commands as the PostgreSQL OS user isn't
going to be acceptable in these environments, but they may still want
the trusted PLs.

> Attached is a small demo piece built by my new co-worker Jeff McCormick
> that I've been hacking lightly on. The idea was to get something a
> step beyond trivial that maps into real-world need, well enough that
> we could get people to agree it was usefully representative.

I really appreciate that. I suspect more work will be needed on the
interface for the actual row-level security code, and work like this
helps point in the direction of what's needed.

> One reason the users I've been talking to feel
> comfortable that they'll be able to build apps usefully with this
> feature is this interface.  It feels more like a pluggable API for
> defining rules than a specific implementation. Congratulations to the
> usual community members who expect complete feature overkill in every
> design.

There's a C-level API that's completely pluggable, for what it's worth.

I'm actually unconvinced that the SQL-level API for row-security is
appropriate - I'm persuaded by comments made on the list that the RLS
syntax and security model needs to be easy enough to use to be useful if
it's to add much on top of what we get with updatable s.b. views,
security barrier, leakproof, etc.

I don't think "run some SQL for every row" fits that. We have that at
the C level in the RLS patch, and I wonder if it should stay there.

I find the heirachical and non-heirachical label security model used in
Teradata to be extremely interesting and worthy of study.

The concept is that there are heirachical label policies (think
"classified", "unclassified", etc) or non-heirachical (mutually
exclusive labels). A user can see a row if they have all the policies
used in a table, and each of the user's policy values is sufficient to
see the row. For non-heiracical policies that means the user and row
must have at least one label in common; for heiracical policies it means
the user label must be greater than or equal to the row label.

That model lets you quite flexibly and easily build complex security
models. It'd work well with PostgreSQL, too: We could implement
something like non-heirachical policies using a system varbit column
that's added whenever a non-heirachical policy gets added to a table,
and simply AND it with the varbit on the user's policy to see if they
can access the row. Or use a compact fixed-width bitfield. Heirachical
policies would use a 1 or 2 byte int system column to store the label.

So we'd have one system column per policy that's been applied to each
table. For users we'd need a catalog table to describe policies, and a
join table mapping users to policies with a user policy value.
Optimization by storing policy values for users directly in pg_role
might be worthwhile.

At the SQL level, an admin would then:

- Define policies
- Assign users labels in policies
- Apply policies to tables

PostgreSQL would be responsible for ensuring that rows written by a user
were labeled with that user's non-heirachical labels, and with their
current session level for their heirachical labels. It'd also be
responsible for enforcing matching on reads.

Internally, we can do all of this with the functoinality provided by
security-barrier views once write support is added. The interface
exposed to the user is made drastically easier to use, though.

Your example would become two non-heirachical security policies:



CREATE SECURITY POLICY colors AS (blue, red, purple);
CREATE SECURITY POLICY shapes AS (triangle, circle, square);

ALTER TABLE color_shapes ADD SECURITY POLICY colors;
ALTER TABLE colors_shapes ADD SECURITY POLICY shapes;


You'd assign users to the policies:

ALTER USER user1 SET SECURITY LABEL FOR POLICY colors TO blue, purple;
ALTER USER user2 SET SECURITY LABEL FOR POLICY colors TO red;

ALTER USER user1 SET SECURITY LABEL FOR POLICY shapes TO circle;
ALTER USER user2 SET SECURITY LABEL FOR POLICY shapes TO triangle;


user1 would now write rows with colors=blue|purple, shapes=circle. user2
would write rows with colors=red, shapes=triangle.

We'd provide session-level control over the current active label set, eg:

    SET (LOCAL) LABELS FOR POLICY ... TO ...

so users can further control the rows they write and see at the session
level, but only within the limits of their authorized labels. Much like
what we have with SET ROLE at the moment. This would be particularly
crucial with heirachical policies, where rows get written at the current
session level.



I'll look at the rest of the mail a bit later; it's Saturday morning
here. I just wanted to raise this for your consideration now, as I think
this offers us a way to provide row-security that doesn't require every
user to just roll their own.



-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-12-18 08:41                                                                         ` Yeb Havinga <yebhavinga@gmail.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Yeb Havinga @ 2013-12-18 08:41 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: pgsql-hackers

On 2013-12-14 05:40, Craig Ringer wrote:
> I find the heirachical and non-heirachical label security model used in
> Teradata to be extremely interesting and worthy of study.
>
> The concept is that there are heirachical label policies (think
> "classified", "unclassified", etc) or non-heirachical (mutually
> exclusive labels). A user can see a row if they have all the policies
> used in a table, and each of the user's policy values is sufficient to
> see the row. For non-heiracical policies that means the user and row
> must have at least one label in common; for heiracical policies it means
> the user label must be greater than or equal to the row label.
>
> That model lets you quite flexibly and easily build complex security
> models. It'd work well with PostgreSQL, too: We could implement
> something like non-heirachical policies using a system varbit column
> that's added whenever a non-heirachical policy gets added to a table,
> and simply AND it with the varbit on the user's policy to see if they
> can access the row. Or use a compact fixed-width bitfield. Heirachical
> policies would use a 1 or 2 byte int system column to store the label.

Is is necessary to fix the attribute type of the security label? The 
label model described above seems to restrictive to support e.g. the 
labels described on p18 of the 'healthcare classification system' (HCS) 
(http://www.hl7.org/documentcenter/public/wg/secure/3.%20HCS%20Guide%20Final%202013%200322%20JMD.pdf). 
The HCS security label is based on the NIST's FIPS188 standard / 
http://www.itl.nist.gov/fipspubs/fip188.htm

regards,
Yeb Havinga



-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2014-01-17 19:27                                                                         ` Gregory Smith <gregsmithpgsql@gmail.com>
  2014-01-20 01:58                                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Gregory Smith @ 2014-01-17 19:27 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

On 12/13/13 11:40 PM, Craig Ringer wrote:
> You may want to check out the updated writable security-barrier views patch.
>
> http://www.postgresql.org/message-id/52AB112B.6020403@2ndquadrant.com
>
> It may offer a path forward for the CF submission for RLS, letting us
> get rid of the var/attr fiddling that many here objected to.

With my advocacy hat on, I'd like to revisit this idea now that there's 
a viable updatable security barrier view submission.  I thought the most 
serious showstopper feedback from the last CF's RLS submission was that 
this needed to be sorted out first.  Reworking KaiGai's submission to 
merge against Dean's new one makes it viable again in my mind, and I'd 
like to continue toward re-reviewing it as part of this CF in that 
light.  Admittedly it's not ideal to try and do that at the same time 
the barrier view patch is being modified, but I see that as a normal CF 
merge of things based on other people's submissions.

I mentioned advocacy because the budding new PostgreSQL test instances 
I'm seeing now will lose a lot of momentum if we end up with no user 
visible RLS features in 9.4.  The pieces we have now can assemble into 
something that's useful, and I don't think that goal is unreasonably far 
away.

-- 
Greg Smith greg.smith@crunchydatasolutions.com
Chief PostgreSQL Evangelist - http://crunchydatasolutions.com/


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
@ 2014-01-20 01:58                                                                           ` Craig Ringer <craig@2ndquadrant.com>
  2014-01-20 06:15                                                                             ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2014-01-20 01:58 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

On 01/18/2014 03:27 AM, Gregory Smith wrote:
> With my advocacy hat on, I'd like to revisit this idea now that there's
> a viable updatable security barrier view submission.  I thought the most
> serious showstopper feedback from the last CF's RLS submission was that
> this needed to be sorted out first.  Reworking KaiGai's submission to
> merge against Dean's new one makes it viable again in my mind, and I'd
> like to continue toward re-reviewing it as part of this CF in that
> light.

I had hoped to have this done weeks ago, but was blocked on getting a
viable approach to updatable security barrier views in place. I really
appreciate Dean, with his greater experience and skill in this area,
looking into it.

As it is I'm spending today reworking the RLS patch on top of the new
approach to updatable security barrier views.

Then it'll be a matter of tests, lots and lots of tests of every weird
corner I can think of.

> Admittedly it's not ideal to try and do that at the same time
> the barrier view patch is being modified, but I see that as a normal CF
> merge of things based on other people's submissions.

I tend to agree - and the whole idea of reworking RLS on top of
updatable security barrier views is that it makes the patch for RLS's UI
and catalogs largely independent from the mechanics of filtering rows.

> I mentioned advocacy because the budding new PostgreSQL test instances
> I'm seeing now will lose a lot of momentum if we end up with no user
> visible RLS features in 9.4.  The pieces we have now can assemble into
> something that's useful, and I don't think that goal is unreasonably far
> away.

If it's possible, getting _something_ into 9.4 would be great. I'm aware
of multiple interested users who originally expected this in 9.3. That
hasn't worked out, but it'd be great to make 9.4.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2014-01-20 01:58                                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2014-01-20 06:15                                                                             ` Craig Ringer <craig@2ndquadrant.com>
  2014-01-24 02:12                                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2014-01-20 06:15 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

On 01/20/2014 09:58 AM, Craig Ringer wrote:
> As it is I'm spending today reworking the RLS patch on top of the new
> approach to updatable security barrier views.

To get that rolling I've split the RLS patch up into chunks, so we can
argue about the catalogs, ALTER syntax, and the actual row-filtering
implementation separately ;-)

It's currently on git@github.com:ringerc/postgres.git in the branch
rls-9.4-split, which is subject to rebasing. I'm still going through it
making sure each chunk at least compiles and preferably passes "make
check".

The first version is on the tag rls-9.4-split-v1, which will remain
static and contains an initial patch-split. The patch series for this
version is attached.

This is a clean tree on top of today's git master, it's not descended
from KaiGai's / Greg's trees. That means it doesn't track RLS's
development details, merge commits, etc. It's just a multi-stage patch
merge of RLS on top of master.

Hopefully it'll be a useful working point. If you omit "guts" commit:

    RLS: Enforce row-security by transforming query plans

you'll get the skeleton of the catalogs and and supporting commands
without the actual planner/optimizer changes, forming the useful basis
for rebuilding RLS on top of Dean's updatable s.b. views patch.

I'm going through the patch series to make sure the split is consistent
and each piece at least builds by its self, then I'm going to rip out
the above-mentioned commit and rework it on top of the updatable
security barriers code.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001-RLS-Make-plan-caching-dependent-on-user-ID.patch (5.3K, ../../52DCBEF1.3010004@2ndquadrant.com/2-0001-RLS-Make-plan-caching-dependent-on-user-ID.patch)
  download | inline diff:
From 5d8145dd0c47fb891139568f82ac87bc2403cfec Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:44:45 +0800
Subject: [PATCH 01/10] RLS: Make plan caching dependent on user ID.

---
 src/backend/optimizer/plan/planner.c |  2 ++
 src/backend/utils/cache/plancache.c  | 32 ++++++++++++++++++++++++++++++++
 src/include/nodes/plannodes.h        |  2 ++
 src/include/nodes/relation.h         |  2 ++
 src/include/utils/plancache.h        |  2 ++
 5 files changed, 40 insertions(+)

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 35bda67..8c12eb9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -177,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -254,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index d492cbb..77de2df 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
@@ -795,6 +796,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -847,6 +858,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -914,6 +927,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -956,6 +987,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 38c039c..35c88cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 8aa40d0..74d0e12 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -99,6 +99,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index b8ca643..ff89700 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
-- 
1.8.3.1

  [text/x-patch] 0002-RLS-add-pg_rowsecurity-catalog.patch (25.1K, ../../52DCBEF1.3010004@2ndquadrant.com/3-0002-RLS-add-pg_rowsecurity-catalog.patch)
  download | inline diff:
From ea6c01b8370f789d75bfd0e3d5d23c63b5e92818 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:07:50 +0800
Subject: [PATCH 02/10] RLS: add pg_rowsecurity catalog

---
 doc/src/sgml/catalogs.sgml           |  65 ++++++++
 src/backend/catalog/Makefile         |   4 +-
 src/backend/catalog/dependency.c     |   8 +
 src/backend/catalog/heap.c           |   1 +
 src/backend/catalog/objectaddress.c  |  50 ++++++
 src/backend/catalog/pg_rowsecurity.c | 287 +++++++++++++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c   |  17 ++-
 src/include/catalog/dependency.h     |   1 +
 src/include/catalog/indexing.h       |   5 +
 src/include/catalog/pg_class.h       |  25 +--
 src/include/catalog/pg_rowsecurity.h |  74 +++++++++
 src/include/utils/rel.h              |   2 +
 12 files changed, 525 insertions(+), 14 deletions(-)
 create mode 100644 src/backend/catalog/pg_rowsecurity.c
 create mode 100644 src/include/catalog/pg_rowsecurity.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..1ca088a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecuritylevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1876,6 +1881,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5157,6 +5172,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowsecurity</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as row-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowsecurity</literal>
+    must be true if a table has row-security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..beb73df 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index e511669..8f95b14 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2321,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 2cf4bc0..5be195b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -799,6 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_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
index 991c02a..870e477 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..7ee4e88
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,287 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   NULL, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   NULL, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a46cfc..8ee5353 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -932,6 +933,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1840,6 +1846,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3166,7 +3174,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4478,6 +4492,7 @@ load_relcache_init_file(bool shared)
 		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/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8948589..de9323e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 0515b75..4f46c5d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -313,6 +313,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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(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_class.h b/src/include/catalog/pg_class.h
index f2fb317..d4aa2f5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
 	bool		relhassubclass; /* has (or has had) derived classes */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -94,7 +95,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					29
+#define Natts_pg_class					30
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -118,12 +119,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhasrules		21
 #define Anum_pg_class_relhastriggers	22
 #define Anum_pg_class_relhassubclass	23
-#define Anum_pg_class_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
+#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
@@ -138,13 +140,14 @@ typedef FormData_pg_class *Form_pg_class;
  * 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_ ));
+
+DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 29 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 t n 3 1 _null_ _null_ ));
+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 t n 3 1 _null_ _null_ ));
+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 29 0 t f f f f t n 3 1 _null_ _null_ ));
+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 0000000..10aed9a
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,74 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	5000
+
+CATALOG(pg_rowsecurity,5000)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9b8a4c9..83a8d03 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -110,6 +111,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * The index chosen as the relation's replication identity or
-- 
1.8.3.1

  [text/x-patch] 0003-RLS-Add-rowsec_relid-to-ResultRelInfo.patch (4.4K, ../../52DCBEF1.3010004@2ndquadrant.com/4-0003-RLS-Add-rowsec_relid-to-ResultRelInfo.patch)
  download | inline diff:
From 6958d4dad091de0ab81696c229da49892046b084 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:28:12 +0800
Subject: [PATCH 03/10] RLS: Add rowsec_relid to ResultRelInfo

This hunk may still be needed in the new approach to filter out rows that've
changed to no longer be visible due to user action r action of triggers;
we'd need to filter the result relation.
---
 src/backend/nodes/copyfuncs.c  | 1 +
 src/backend/nodes/equalfuncs.c | 1 +
 src/backend/nodes/outfuncs.c   | 1 +
 src/backend/nodes/readfuncs.c  | 1 +
 src/include/nodes/execnodes.h  | 4 ++++
 src/include/nodes/parsenodes.h | 8 +++++++-
 6 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bb356d0..52bd8f5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1977,6 +1977,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(functions);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5908d9a..b9b0de2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2269,6 +2269,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(functions);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 568c3b8..cfa4cb3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2376,6 +2376,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c22acd5..335579f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1218,6 +1218,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..83637eb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -308,6 +308,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -329,6 +331,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 846c31a..48c6d0f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -741,6 +742,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
-- 
1.8.3.1

  [text/x-patch] 0004-Add-ALTER-TABLE-commands-for-row-security.patch (13.4K, ../../52DCBEF1.3010004@2ndquadrant.com/5-0004-Add-ALTER-TABLE-commands-for-row-security.patch)
  download | inline diff:
From 6a482c0a9426dbfa25735bbbc888de9e8b5474b1 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:37:47 +0800
Subject: [PATCH 04/10] Add ALTER TABLE commands for row security

---
 doc/src/sgml/ref/alter_table.sgml    | 38 +++++++++++++++++++++++++++
 src/backend/catalog/pg_rowsecurity.c | 50 ++++++++++++++++++++++++++++++++++++
 src/backend/commands/event_trigger.c |  1 +
 src/backend/commands/tablecmds.c     | 26 +++++++++++++++++++
 src/backend/parser/gram.y            | 24 +++++++++++++++++
 src/backend/parser/parse_agg.c       |  6 +++++
 src/backend/parser/parse_expr.c      |  3 +++
 src/include/catalog/pg_rowsecurity.h |  2 ++
 src/include/nodes/parsenodes.h       |  2 ++
 src/include/parser/parse_node.h      |  3 ++-
 10 files changed, 154 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..79f37d8 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,6 +69,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
     REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -76,6 +78,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -581,6 +585,26 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
     <term><literal>REPLICA IDENTITY</literal></term>
     <listitem>
      <para>
@@ -840,6 +864,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/src/backend/catalog/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
index 7ee4e88..34d33e8 100644
--- a/src/backend/catalog/pg_rowsecurity.c
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -285,3 +285,53 @@ RemoveRowSecurityById(Oid rowsecId)
 	systable_endscan(sscan);
 	heap_close(catalog, RowExclusiveLock);
 }
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 59f0842..51a1c0f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -995,6 +995,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 26a4613..b941f7f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2789,6 +2790,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3164,6 +3167,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3449,6 +3454,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
 		case AT_ReplicaIdentity:
 			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
 			break;
@@ -7814,6 +7824,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 12a6beb..2bf0010 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -260,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 	   replica_identity
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2191,6 +2192,23 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+				}
 			/* ALTER TABLE <name> REPLICA IDENTITY  */
 			| REPLICA IDENTITY_P replica_identity
 				{
@@ -2301,6 +2319,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..333b557 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -333,6 +333,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -662,6 +665,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 81c9338..e289e77 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1462,6 +1462,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2643,6 +2644,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/include/catalog/pg_rowsecurity.h b/src/include/catalog/pg_rowsecurity.h
index 10aed9a..798d556 100644
--- a/src/include/catalog/pg_rowsecurity.h
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -69,6 +69,8 @@ typedef struct
 } RowSecurityDesc;
 
 extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
 extern void	RemoveRowSecurityById(Oid relationId);
 
 #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48c6d0f..3a918bb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1322,6 +1322,8 @@ typedef enum AlterTableType
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
 	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 85598e8..7218e60 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -63,7 +63,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY,			/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
-- 
1.8.3.1

  [text/x-patch] 0005-RLS-pg_dump-support-for-dumping-rowsecurity-catalogs.patch (13.9K, ../../52DCBEF1.3010004@2ndquadrant.com/6-0005-RLS-pg_dump-support-for-dumping-rowsecurity-catalogs.patch)
  download | inline diff:
From a3572eb6ff17e712d21109bc87361b30da6e699b Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:45:56 +0800
Subject: [PATCH 05/10] RLS: pg_dump support for dumping rowsecurity catalogs

---
 src/bin/pg_dump/common.c             |   4 +
 src/bin/pg_dump/pg_backup_archiver.c |   1 +
 src/bin/pg_dump/pg_dump.c            | 151 ++++++++++++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h            |  13 ++-
 src/bin/pg_dump/pg_dump_sort.c       |   5 ++
 5 files changed, 169 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4d35ae5..fd9e6cd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		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
index 7fc0288..6502d93 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3137,6 +3137,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 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
index 3862f05..6939852 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -250,6 +250,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2714,6 +2715,134 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		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.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_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].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;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s;\n", 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,
@@ -4242,6 +4371,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4293,10 +4423,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
-						  "c.relfrozenxid, tc.oid AS toid, "
-						  "tc.relfrozenxid AS tfrozenxid, "
-						  "c.relpersistence, c.relispopulated, "
-						  "c.relreplident, c.relpages, "
+						  "c.relhasrowsecurity, "
 						  "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, "
@@ -4332,6 +4459,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4371,6 +4499,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
@@ -4408,6 +4537,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4444,6 +4574,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4480,6 +4611,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4516,6 +4648,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4552,6 +4685,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4584,6 +4718,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4611,6 +4746,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4648,6 +4784,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4695,6 +4832,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4744,6 +4882,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].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));
@@ -7861,6 +8000,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -15147,6 +15289,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index c8dac35..de10c95 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -245,6 +246,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -484,6 +486,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -575,5 +585,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index bda3535..718007d 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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)",
-- 
1.8.3.1

  [text/x-patch] 0006-RLS-psql-support-for-reporting-row-security-constrai.patch (1.4K, ../../52DCBEF1.3010004@2ndquadrant.com/7-0006-RLS-psql-support-for-reporting-row-security-constrai.patch)
  download | inline diff:
From c3e0a70e1b3e77a9ab50d8062492e0d2e2a567a0 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:45:24 +0800
Subject: [PATCH 06/10] RLS: psql support for reporting row security
 constraints

---
 src/bin/psql/describe.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0d4b151..e99b72a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2764,6 +2764,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBufferStr(&buf,
@@ -2773,6 +2777,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-- 
1.8.3.1

  [text/x-patch] 0007-RLS-Enforce-row-security-by-transforming-query-plans.patch (45.6K, ../../52DCBEF1.3010004@2ndquadrant.com/8-0007-RLS-Enforce-row-security-by-transforming-query-plans.patch)
  download | inline diff:
From 668e14b2de0d78fe5722b227071151a68ed759a1 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:49:56 +0800
Subject: [PATCH 07/10] RLS: Enforce row-security by transforming query plans

---
 src/backend/executor/execMain.c          |  27 +-
 src/backend/nodes/copyfuncs.c            |   2 +
 src/backend/nodes/equalfuncs.c           |   2 +
 src/backend/nodes/nodeFuncs.c            |  12 +-
 src/backend/nodes/outfuncs.c             |   2 +
 src/backend/nodes/readfuncs.c            |   1 +
 src/backend/optimizer/plan/planner.c     |  21 +-
 src/backend/optimizer/prep/preptlist.c   |  68 ++-
 src/backend/optimizer/prep/prepunion.c   |  95 +++-
 src/backend/optimizer/util/Makefile      |   3 +-
 src/backend/optimizer/util/rowsecurity.c | 744 +++++++++++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c     |  16 +
 src/backend/utils/adt/ri_triggers.c      |  13 +-
 src/include/miscadmin.h                  |   1 +
 src/include/nodes/nodeFuncs.h            |   1 +
 src/include/nodes/parsenodes.h           |   4 +-
 src/include/nodes/relation.h             |   4 +
 src/include/optimizer/rowsecurity.h      |  27 ++
 src/include/rewrite/rewriteHandler.h     |   1 +
 19 files changed, 1016 insertions(+), 28 deletions(-)
 create mode 100644 src/backend/optimizer/util/rowsecurity.c
 create mode 100644 src/include/optimizer/rowsecurity.h

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 6b5f198..0041c23 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -789,8 +789,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	foreach(l, plannedstmt->rowMarks)
 	{
 		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
-		Oid			relid;
-		Relation	relation;
+		RangeTblEntry *rte = NULL;
+		Relation	relation = NULL;
+		LOCKMODE	lockmode = NoLock;
 		ExecRowMark *erm;
 
 		/* ignore "parent" rowmarks; they are irrelevant at runtime */
@@ -803,27 +804,33 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			case ROW_MARK_NOKEYEXCLUSIVE:
 			case ROW_MARK_SHARE:
 			case ROW_MARK_KEYSHARE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, RowShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = RowShareLock;
 				break;
 			case ROW_MARK_REFERENCE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, AccessShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = AccessShareLock;
 				break;
 			case ROW_MARK_COPY:
 				/* there's no real table here ... */
-				relation = NULL;
 				break;
 			default:
 				elog(ERROR, "unrecognized markType: %d", rc->markType);
-				relation = NULL;	/* keep compiler quiet */
 				break;
 		}
 
 		/* Check that relation is a legal target for marking */
-		if (relation)
+		if (rte)
+		{
+			if (rte->rtekind == RTE_RELATION)
+				relation = heap_open(rte->relid, lockmode);
+			else
+			{
+				Assert(rte->rtekind == RTE_SUBQUERY);
+				relation = heap_open(rte->rowsec_relid, lockmode);
+			}
 			CheckValidRowMarkRel(relation, rc->markType);
-
+		}
 		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
 		erm->relation = relation;
 		erm->rti = rc->rti;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 52bd8f5..3a8b60c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1936,6 +1936,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -2467,6 +2468,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9b0de2..cb2abf6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -814,6 +814,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -849,6 +850,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 123f2a6..fa8f040 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1948,8 +1948,11 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker((Node *) query->withCheckOptions, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2686,7 +2689,10 @@ query_tree_mutator(Query *query,
 
 	MUTATE(query->targetList, query->targetList, List *);
 	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cfa4cb3..500faeb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1925,6 +1925,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2241,6 +2242,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfoString(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 335579f..884f45c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -201,6 +201,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8c12eb9..63f0ec2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -406,6 +407,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -898,6 +912,8 @@ inheritance_planner(PlannerInfo *root)
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -960,7 +976,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build lists of per-relation WCO and RETURNING targetlists */
 		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index ee773b8..5e4a010 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,49 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+							   Index result_relation, Index source_relation,
+							   List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -51,6 +92,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			source_relation = parse->sourceRelation;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	ListCell   *lc;
@@ -73,8 +115,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  result_relation,
+								  source_relation,
+								  range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +142,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +159,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -195,7 +243,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
  */
 static List *
 expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table)
+				  Index result_relation, Index source_relation,
+				  List *range_table)
 {
 	List	   *new_tlist = NIL;
 	ListCell   *tlist_item;
@@ -218,6 +267,9 @@ expand_targetlist(List *tlist, int command_type,
 
 	numattrs = RelationGetNumberOfAttributes(rel);
 
+	if (source_relation == 0)
+		source_relation = result_relation;
+
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
 		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
@@ -298,8 +350,10 @@ expand_targetlist(List *tlist, int command_type,
 				case CMD_UPDATE:
 					if (!att_tup->attisdropped)
 					{
-						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+						new_expr = (Node *) makeVar(source_relation,
+													lookup_varattno(attrno,
+														source_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 52dcc72..9bd97e7 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference pseudo columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index c54d0a6..d42a285 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \
-       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o
+       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o \
+       rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..c2e5a49
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,744 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_pseudo_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_pseudo_column(PlannerInfo *root,
+					 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds pseudo columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focus on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		if (context->vartrans[var->varno] > 0)
+		{
+			Index	rtindex_trans = context->vartrans[var->varno];
+
+			rte = rt_fetch(rtindex_trans, rtable);
+			Assert(rte->rtekind == RTE_SUBQUERY &&
+				   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+			var->varno = var->varnoold = rtindex_trans;
+			var->varattno = lookup_pseudo_column(context->root, rte,
+													 var->varattno);
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (rte->rtekind == RTE_RELATION && rte->inh)
+			{
+				foreach (cell, context->root->append_rel_list)
+				{
+					AppendRelInfo  *appinfo = lfirst(cell);
+					RangeTblEntry  *child_rte;
+
+					if (appinfo->parent_relid != var->varno)
+						continue;
+
+					child_rte = rt_fetch(appinfo->child_relid, rtable);
+					if (child_rte->rtekind == RTE_SUBQUERY &&
+						child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+						(void) lookup_pseudo_column(context->root,
+													child_rte,
+													var->varattno);
+				}
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains pseudo columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/*
+	 * Fix up PlanRowMark if needed, then add references to 'tableoid' and
+	 * 'ctid' that shall be added to handle row-level locking.
+	 * Also see preprocess_targetlist() that adds some junk attributes.
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_pseudo_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_pseudo_column(root, newrte, TableOidAttributeNumber);
+	}
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Relation		rel;
+	Expr		   *qual;
+	int				flags;
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		/*
+		 * In case when relation has inherited children, we try to apply
+		 * row-level security policy of them if configured.
+		 * In addition to regular replacement with a sub-query, we need
+		 * to adjust rtindex of AppendRelInfo and varno of translated_vars.
+		 * It makes sub-queries perform like regular relations being
+		 * inherited from a particular parent relation. So, a table scan
+		 * may have underlying a relation scan and two sub-query scans for
+		 * instance. If it is result relation of UPDATE or DELETE command,
+		 * rtindex to the original relation (regular relation) has to be
+		 * kept because sub-query cannot perform as an updatable relation.
+		 * So, we save it on child_result of AppendRelInfo; that shall be
+		 * used to track relations to be modified at inheritance_planner().
+		 */
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				/*
+				 * Save the rtindex of actual relation to be modified,
+				 * if parent relation is result relation of this query.
+				 */
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				/* Adjust varno to reference pseudo columns */
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var	   *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * It walks on the given join-tree to replace relations with row-level
+ * security policy by a simple sub-query.
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying pseudo column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 0b13645..2f56211 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3282,3 +3282,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1e1e616..68be040 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f133e5f..1c97bd9 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -272,6 +272,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index f9afdb6..9ed3796 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a918bb..c8ce32a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -113,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 74d0e12..252e45f 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1438,6 +1438,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index e4027bd..7bd334c 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
-- 
1.8.3.1

  [text/x-patch] 0008-RLS-Enforce-row-security-on-COPY.patch (4.8K, ../../52DCBEF1.3010004@2ndquadrant.com/9-0008-RLS-Enforce-row-security-on-COPY.patch)
  download | inline diff:
From 4e4fa295080cc497eb4db783b2218050ac68ecf3 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:50:23 +0800
Subject: [PATCH 08/10] RLS: Enforce row-security on COPY

---
 src/backend/commands/copy.c | 90 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f91f164..8e98fd2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
-- 
1.8.3.1

  [text/x-patch] 0009-RLS-Regression-tests.patch (43.5K, ../../52DCBEF1.3010004@2ndquadrant.com/10-0009-RLS-Regression-tests.patch)
  download | inline diff:
From 6924943ad9b682526de30b39b8b481e6b3c2e3b9 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 12:58:29 +0800
Subject: [PATCH 09/10] RLS: Regression tests

---
 src/test/regress/expected/rowsecurity.out  | 950 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   1 +
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowsecurity.sql       | 298 +++++++++
 5 files changed, 1251 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/rowsecurity.out
 create mode 100644 src/test/regress/sql/rowsecurity.sql

diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..04591df
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,950 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a62a3e3..a41344b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -121,6 +121,7 @@ pg_pltemplate|t
 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
index 5758b07..f7963fc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock replica_identity
+test: privileges rowsecurity security_label collate matview lock replica_identity
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 78348f5..51c4c12 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -94,6 +94,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;
-- 
1.8.3.1

  [text/x-patch] 0010-RLS-Supplimental-docs.patch (7.0K, ../../52DCBEF1.3010004@2ndquadrant.com/11-0010-RLS-Supplimental-docs.patch)
  download | inline diff:
From 63e90965e8ce0d89113002c6df283761ffc6adfc Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:50:37 +0800
Subject: [PATCH 10/10] RLS: Supplimental docs

---
 doc/src/sgml/user-manag.sgml | 145 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)

diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c764189 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.4 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
-- 
1.8.3.1

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2014-01-20 01:58                                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-20 06:15                                                                             ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2014-01-24 02:12                                                                               ` Craig Ringer <craig@2ndquadrant.com>
  2014-01-24 03:15                                                                                 ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 1 reply; 165+ messages in thread

From: Craig Ringer @ 2014-01-24 02:12 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

(Re-sending; I forgot to cc the list)

On 01/20/2014 02:15 PM, Craig Ringer wrote:
> On 01/20/2014 09:58 AM, Craig Ringer wrote:
>> As it is I'm spending today reworking the RLS patch on top of the new
>> approach to updatable security barrier views.
> 
> To get that rolling I've split the RLS patch up into chunks, so we can
> argue about the catalogs, ALTER syntax, and the actual row-filtering
> implementation separately ;-)
> 
> It's currently on git@github.com:ringerc/postgres.git in the branch
> rls-9.4-split, which is subject to rebasing. I'm still going through it
> making sure each chunk at least compiles and preferably passes "make
> check".

That branch is now pretty stable, and passes checks at every stage up to
the new RLS regression tests. I've pushed a new version to branch
rls-9.4-split. Further updates will rebase this branch.

The tag rls-9.4-split-v5 identifies this particular push, and won't get
rebased away.

I found a number of merge errors and problems that I've corrected and
incorporated into the history, including a bad pg_class.h edit, a
missing break; in tablecmds.c, a missing Node* assignment in the parser
grammar for RESET ROW SECURITY, etc.

A number of concerns remain outstanding, including those Greg listed
except for the regression tests, which I've fixed, and rebasing on top
of current master.

-Continue to expand the functional tests

-Is there enough information about row security available in psql
 output?  For example, there's nothing in "\d" output that suggests it
 might exist.  pg_rowsecurity is a monster to try and read.

-Documentation needs plenty of editing, which I can take care of.


Additionally, the issue I found earlier remains outstanding:

- Fails to deal with user-id change during portal execution in (eg)
  SECURITY DEFINER funcs returning refcursor, or SQL cursors open
  across SET SESSION AUTHORIZATION. `current_user` returns value
  after change, not original value, causing issues with RLS predicates.


Now that the patch is split up into stages I'm going to work on
replacing patches 7 and 8 (query plan transformation, COPY support) with
an approach based on Dean's updaable security barriers code.

If anyone else picks up work on this, **please** try to keep this patch
series intact, or even better, move the regression tests back into the
relevant patches as you go. If you send an updated version of the RLS
patch please send a git ref too; that'll make it much easier to pull
your changes and integrate them into the relevant patch in the series
with appropriate "git rebase --interactive" (ab)use.



-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [text/x-patch] 0001-RLS-Make-plan-caching-dependent-on-user-ID.patch (5.3K, ../../52E1CC04.8040907@2ndquadrant.com/2-0001-RLS-Make-plan-caching-dependent-on-user-ID.patch)
  download | inline diff:
From 5d8145dd0c47fb891139568f82ac87bc2403cfec Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:44:45 +0800
Subject: [PATCH 01/10] RLS: Make plan caching dependent on user ID.

---
 src/backend/optimizer/plan/planner.c |  2 ++
 src/backend/utils/cache/plancache.c  | 32 ++++++++++++++++++++++++++++++++
 src/include/nodes/plannodes.h        |  2 ++
 src/include/nodes/relation.h         |  2 ++
 src/include/utils/plancache.h        |  2 ++
 5 files changed, 40 insertions(+)

diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 35bda67..8c12eb9 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -177,6 +177,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -254,6 +255,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index d492cbb..77de2df 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
@@ -795,6 +796,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -847,6 +858,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -914,6 +927,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -956,6 +987,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 38c039c..35c88cc 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 8aa40d0..74d0e12 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -99,6 +99,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index b8ca643..ff89700 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
-- 
1.8.3.1

  [text/x-patch] 0002-RLS-add-pg_rowsecurity-catalog.patch (26.3K, ../../52E1CC04.8040907@2ndquadrant.com/3-0002-RLS-add-pg_rowsecurity-catalog.patch)
  download | inline diff:
From ad60419074deef9f3d057e395ff08375afd9eeaa Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:07:50 +0800
Subject: [PATCH 02/10] RLS: add pg_rowsecurity catalog

---
 doc/src/sgml/catalogs.sgml                 |  65 +++++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |   8 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/objectaddress.c        |  50 +++++
 src/backend/catalog/pg_rowsecurity.c       | 287 +++++++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c         |  17 +-
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   5 +
 src/include/catalog/pg_class.h             |  25 +--
 src/include/catalog/pg_rowsecurity.h       |  74 ++++++++
 src/include/parser/parse_node.h            |   3 +-
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/sanity_check.out |   1 +
 14 files changed, 528 insertions(+), 15 deletions(-)
 create mode 100644 src/backend/catalog/pg_rowsecurity.c
 create mode 100644 src/include/catalog/pg_rowsecurity.h

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2230c93..1ca088a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecuritylevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1876,6 +1881,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5157,6 +5172,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowsecurity</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as row-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowsecurity</literal>
+    must be true if a table has row-security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..beb73df 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index e511669..8f95b14 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2321,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 2cf4bc0..5be195b 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -799,6 +799,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_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
index 991c02a..870e477 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..7ee4e88
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,287 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   NULL, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   NULL, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2a46cfc..8ee5353 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -932,6 +933,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1840,6 +1846,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3166,7 +3174,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4478,6 +4492,7 @@ load_relcache_init_file(bool shared)
 		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/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8948589..de9323e 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 0515b75..4f46c5d 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -313,6 +313,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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(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_class.h b/src/include/catalog/pg_class.h
index f2fb317..bbbf62d 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -65,6 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
 	bool		relhassubclass; /* has (or has had) derived classes */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relispopulated; /* matview currently holds query results */
 	char		relreplident;	/* see REPLICA_IDENTITY_xxx constants  */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -94,7 +95,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					29
+#define Natts_pg_class					30
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -118,12 +119,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhasrules		21
 #define Anum_pg_class_relhastriggers	22
 #define Anum_pg_class_relhassubclass	23
-#define Anum_pg_class_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
+#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
@@ -138,13 +140,14 @@ typedef FormData_pg_class *Form_pg_class;
  * 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_ ));
+
+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 t n 3 1 _null_ _null_ ));
+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 t n 3 1 _null_ _null_ ));
+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 29 0 t f f f f t n 3 1 _null_ _null_ ));
+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 0000000..10aed9a
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,74 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	5000
+
+CATALOG(pg_rowsecurity,5000)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 85598e8..9c7c382 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -63,7 +63,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY 		/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 9b8a4c9..83a8d03 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -110,6 +111,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * The index chosen as the relation's replication identity or
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index a62a3e3..a41344b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -121,6 +121,7 @@ pg_pltemplate|t
 pg_proc|t
 pg_range|t
 pg_rewrite|t
+pg_rowsecurity|t
 pg_seclabel|t
 pg_shdepend|t
 pg_shdescription|t
-- 
1.8.3.1

  [text/x-patch] 0003-RLS-Add-rowsec_relid-to-ResultRelInfo.patch (4.4K, ../../52E1CC04.8040907@2ndquadrant.com/4-0003-RLS-Add-rowsec_relid-to-ResultRelInfo.patch)
  download | inline diff:
From 76847f695ce425117783e722dc49d22b1464ef64 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:28:12 +0800
Subject: [PATCH 03/10] RLS: Add rowsec_relid to ResultRelInfo

This hunk may still be needed in the new approach to filter out rows that've
changed to no longer be visible due to user action r action of triggers;
we'd need to filter the result relation.
---
 src/backend/nodes/copyfuncs.c  | 1 +
 src/backend/nodes/equalfuncs.c | 1 +
 src/backend/nodes/outfuncs.c   | 1 +
 src/backend/nodes/readfuncs.c  | 1 +
 src/include/nodes/execnodes.h  | 4 ++++
 src/include/nodes/parsenodes.h | 8 +++++++-
 6 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bb356d0..52bd8f5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1977,6 +1977,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(functions);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 5908d9a..b9b0de2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2269,6 +2269,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(functions);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 568c3b8..cfa4cb3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2376,6 +2376,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index c22acd5..335579f 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1218,6 +1218,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a301a08..83637eb 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -308,6 +308,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -329,6 +331,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 846c31a..48c6d0f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -741,6 +742,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
-- 
1.8.3.1

  [text/x-patch] 0004-Add-ALTER-TABLE-commands-for-row-security.patch (12.7K, ../../52E1CC04.8040907@2ndquadrant.com/5-0004-Add-ALTER-TABLE-commands-for-row-security.patch)
  download | inline diff:
From 8a2a621c72464f06f0633b019f62d5dd13f8a10b Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:37:47 +0800
Subject: [PATCH 04/10] Add ALTER TABLE commands for row security

---
 doc/src/sgml/ref/alter_table.sgml    | 38 +++++++++++++++++++++++++++
 src/backend/catalog/pg_rowsecurity.c | 50 ++++++++++++++++++++++++++++++++++++
 src/backend/commands/event_trigger.c |  1 +
 src/backend/commands/tablecmds.c     | 27 +++++++++++++++++++
 src/backend/parser/gram.y            | 25 ++++++++++++++++++
 src/backend/parser/parse_agg.c       |  6 +++++
 src/backend/parser/parse_expr.c      |  3 +++
 src/include/catalog/pg_rowsecurity.h |  2 ++
 src/include/nodes/parsenodes.h       |  2 ++
 9 files changed, 154 insertions(+)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 89649a2..79f37d8 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,6 +69,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
     REPLICA IDENTITY {DEFAULT | USING INDEX <replaceable class="PARAMETER">index_name</replaceable> | FULL | NOTHING}
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
@@ -76,6 +78,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -581,6 +585,26 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
     <term><literal>REPLICA IDENTITY</literal></term>
     <listitem>
      <para>
@@ -840,6 +864,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/src/backend/catalog/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
index 7ee4e88..34d33e8 100644
--- a/src/backend/catalog/pg_rowsecurity.c
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -285,3 +285,53 @@ RemoveRowSecurityById(Oid rowsecId)
 	systable_endscan(sscan);
 	heap_close(catalog, RowExclusiveLock);
 }
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 59f0842..51a1c0f 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -995,6 +995,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 26a4613..f02ce72 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2789,6 +2790,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3164,6 +3167,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3449,6 +3454,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_ReplicaIdentity:
 			ATExecReplicaIdentity(rel, (ReplicaIdentityStmt *) cmd->def, lockmode);
 			break;
@@ -7814,6 +7825,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 12a6beb..ba1ac81 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -260,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 	   replica_identity
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2191,6 +2192,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> REPLICA IDENTITY  */
 			| REPLICA IDENTITY_P replica_identity
 				{
@@ -2301,6 +2320,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 9613e2a..333b557 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -333,6 +333,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -662,6 +665,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 81c9338..e289e77 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1462,6 +1462,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2643,6 +2644,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/include/catalog/pg_rowsecurity.h b/src/include/catalog/pg_rowsecurity.h
index 10aed9a..798d556 100644
--- a/src/include/catalog/pg_rowsecurity.h
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -69,6 +69,8 @@ typedef struct
 } RowSecurityDesc;
 
 extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
 extern void	RemoveRowSecurityById(Oid relationId);
 
 #endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48c6d0f..3a918bb 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1322,6 +1322,8 @@ typedef enum AlterTableType
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
 	AT_ReplicaIdentity,			/* REPLICA IDENTITY */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
-- 
1.8.3.1

  [text/x-patch] 0005-RLS-pg_dump-support-for-dumping-rowsecurity-catalogs.patch (13.9K, ../../52E1CC04.8040907@2ndquadrant.com/6-0005-RLS-pg_dump-support-for-dumping-rowsecurity-catalogs.patch)
  download | inline diff:
From bb8455bcf9dbaf5ef9cdf2898efcfde6eb6edecf Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:45:56 +0800
Subject: [PATCH 05/10] RLS: pg_dump support for dumping rowsecurity catalogs

---
 src/bin/pg_dump/common.c             |   4 +
 src/bin/pg_dump/pg_backup_archiver.c |   1 +
 src/bin/pg_dump/pg_dump.c            | 151 ++++++++++++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h            |  13 ++-
 src/bin/pg_dump/pg_dump_sort.c       |   5 ++
 5 files changed, 169 insertions(+), 5 deletions(-)

diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4d35ae5..fd9e6cd 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		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
index 7fc0288..6502d93 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3137,6 +3137,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 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
index 3862f05..6939852 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -250,6 +250,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2714,6 +2715,134 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		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.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_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].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;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s;\n", 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,
@@ -4242,6 +4371,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4293,10 +4423,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
-						  "c.relfrozenxid, tc.oid AS toid, "
-						  "tc.relfrozenxid AS tfrozenxid, "
-						  "c.relpersistence, c.relispopulated, "
-						  "c.relreplident, c.relpages, "
+						  "c.relhasrowsecurity, "
 						  "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, "
@@ -4332,6 +4459,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4371,6 +4499,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
@@ -4408,6 +4537,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4444,6 +4574,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4480,6 +4611,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4516,6 +4648,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4552,6 +4685,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4584,6 +4718,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4611,6 +4746,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4648,6 +4784,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4695,6 +4832,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4744,6 +4882,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].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));
@@ -7861,6 +8000,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -15147,6 +15289,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index c8dac35..de10c95 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -245,6 +246,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -484,6 +486,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -575,5 +585,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index bda3535..718007d 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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)",
-- 
1.8.3.1

  [text/x-patch] 0006-RLS-psql-support-for-reporting-row-security-constrai.patch (1.4K, ../../52E1CC04.8040907@2ndquadrant.com/7-0006-RLS-psql-support-for-reporting-row-security-constrai.patch)
  download | inline diff:
From db96cb2be9c1d71d7b5f7f3fe21fd636b0e7a9fb Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:45:24 +0800
Subject: [PATCH 06/10] RLS: psql support for reporting row security
 constraints

---
 src/bin/psql/describe.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0d4b151..e99b72a 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2764,6 +2764,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBufferStr(&buf,
@@ -2773,6 +2777,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBufferStr(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
-- 
1.8.3.1

  [text/x-patch] 0007-RLS-Enforce-row-security-by-transforming-query-plans.patch (45.6K, ../../52E1CC04.8040907@2ndquadrant.com/8-0007-RLS-Enforce-row-security-by-transforming-query-plans.patch)
  download | inline diff:
From a88e62608515b82eccc78d005e4f838ceb8b51e3 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:49:56 +0800
Subject: [PATCH 07/10] RLS: Enforce row-security by transforming query plans

---
 src/backend/executor/execMain.c          |  27 +-
 src/backend/nodes/copyfuncs.c            |   2 +
 src/backend/nodes/equalfuncs.c           |   2 +
 src/backend/nodes/nodeFuncs.c            |  12 +-
 src/backend/nodes/outfuncs.c             |   2 +
 src/backend/nodes/readfuncs.c            |   1 +
 src/backend/optimizer/plan/planner.c     |  21 +-
 src/backend/optimizer/prep/preptlist.c   |  68 ++-
 src/backend/optimizer/prep/prepunion.c   |  95 +++-
 src/backend/optimizer/util/Makefile      |   3 +-
 src/backend/optimizer/util/rowsecurity.c | 744 +++++++++++++++++++++++++++++++
 src/backend/rewrite/rewriteHandler.c     |  16 +
 src/backend/utils/adt/ri_triggers.c      |  13 +-
 src/include/miscadmin.h                  |   1 +
 src/include/nodes/nodeFuncs.h            |   1 +
 src/include/nodes/parsenodes.h           |   4 +-
 src/include/nodes/relation.h             |   4 +
 src/include/optimizer/rowsecurity.h      |  27 ++
 src/include/rewrite/rewriteHandler.h     |   1 +
 19 files changed, 1016 insertions(+), 28 deletions(-)
 create mode 100644 src/backend/optimizer/util/rowsecurity.c
 create mode 100644 src/include/optimizer/rowsecurity.h

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 6b5f198..0041c23 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -789,8 +789,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	foreach(l, plannedstmt->rowMarks)
 	{
 		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
-		Oid			relid;
-		Relation	relation;
+		RangeTblEntry *rte = NULL;
+		Relation	relation = NULL;
+		LOCKMODE	lockmode = NoLock;
 		ExecRowMark *erm;
 
 		/* ignore "parent" rowmarks; they are irrelevant at runtime */
@@ -803,27 +804,33 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			case ROW_MARK_NOKEYEXCLUSIVE:
 			case ROW_MARK_SHARE:
 			case ROW_MARK_KEYSHARE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, RowShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = RowShareLock;
 				break;
 			case ROW_MARK_REFERENCE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, AccessShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = AccessShareLock;
 				break;
 			case ROW_MARK_COPY:
 				/* there's no real table here ... */
-				relation = NULL;
 				break;
 			default:
 				elog(ERROR, "unrecognized markType: %d", rc->markType);
-				relation = NULL;	/* keep compiler quiet */
 				break;
 		}
 
 		/* Check that relation is a legal target for marking */
-		if (relation)
+		if (rte)
+		{
+			if (rte->rtekind == RTE_RELATION)
+				relation = heap_open(rte->relid, lockmode);
+			else
+			{
+				Assert(rte->rtekind == RTE_SUBQUERY);
+				relation = heap_open(rte->rowsec_relid, lockmode);
+			}
 			CheckValidRowMarkRel(relation, rc->markType);
-
+		}
 		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
 		erm->relation = relation;
 		erm->rti = rc->rti;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 52bd8f5..3a8b60c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1936,6 +1936,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -2467,6 +2468,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index b9b0de2..cb2abf6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -814,6 +814,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -849,6 +850,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 123f2a6..fa8f040 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1948,8 +1948,11 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker((Node *) query->withCheckOptions, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2686,7 +2689,10 @@ query_tree_mutator(Query *query,
 
 	MUTATE(query->targetList, query->targetList, List *);
 	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cfa4cb3..500faeb 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1925,6 +1925,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2241,6 +2242,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfoString(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 335579f..884f45c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -201,6 +201,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 8c12eb9..63f0ec2 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -406,6 +407,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -898,6 +912,8 @@ inheritance_planner(PlannerInfo *root)
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.append_rel_list, rti, newrti, 0);
+ 					if (subroot.parse->sourceRelation == rti)
+ 						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -960,7 +976,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build lists of per-relation WCO and RETURNING targetlists */
 		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index ee773b8..5e4a010 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,49 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+							   Index result_relation, Index source_relation,
+							   List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -51,6 +92,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			source_relation = parse->sourceRelation;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	ListCell   *lc;
@@ -73,8 +115,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  result_relation,
+								  source_relation,
+								  range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +142,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +159,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -195,7 +243,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
  */
 static List *
 expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table)
+				  Index result_relation, Index source_relation,
+				  List *range_table)
 {
 	List	   *new_tlist = NIL;
 	ListCell   *tlist_item;
@@ -218,6 +267,9 @@ expand_targetlist(List *tlist, int command_type,
 
 	numattrs = RelationGetNumberOfAttributes(rel);
 
+	if (source_relation == 0)
+		source_relation = result_relation;
+
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
 		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
@@ -298,8 +350,10 @@ expand_targetlist(List *tlist, int command_type,
 				case CMD_UPDATE:
 					if (!att_tup->attisdropped)
 					{
-						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+						new_expr = (Node *) makeVar(source_relation,
+													lookup_varattno(attrno,
+														source_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 52dcc72..9bd97e7 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference pseudo columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index c54d0a6..d42a285 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,7 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o orclauses.o pathnode.o placeholder.o \
-       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o
+       plancat.o predtest.o relnode.o restrictinfo.o tlist.o var.o \
+       rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..c2e5a49
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,744 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_pseudo_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_pseudo_column(PlannerInfo *root,
+					 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds pseudo columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focus on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		if (context->vartrans[var->varno] > 0)
+		{
+			Index	rtindex_trans = context->vartrans[var->varno];
+
+			rte = rt_fetch(rtindex_trans, rtable);
+			Assert(rte->rtekind == RTE_SUBQUERY &&
+				   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+			var->varno = var->varnoold = rtindex_trans;
+			var->varattno = lookup_pseudo_column(context->root, rte,
+													 var->varattno);
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (rte->rtekind == RTE_RELATION && rte->inh)
+			{
+				foreach (cell, context->root->append_rel_list)
+				{
+					AppendRelInfo  *appinfo = lfirst(cell);
+					RangeTblEntry  *child_rte;
+
+					if (appinfo->parent_relid != var->varno)
+						continue;
+
+					child_rte = rt_fetch(appinfo->child_relid, rtable);
+					if (child_rte->rtekind == RTE_SUBQUERY &&
+						child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+						(void) lookup_pseudo_column(context->root,
+													child_rte,
+													var->varattno);
+				}
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains pseudo columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/*
+	 * Fix up PlanRowMark if needed, then add references to 'tableoid' and
+	 * 'ctid' that shall be added to handle row-level locking.
+	 * Also see preprocess_targetlist() that adds some junk attributes.
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_pseudo_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_pseudo_column(root, newrte, TableOidAttributeNumber);
+	}
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Relation		rel;
+	Expr		   *qual;
+	int				flags;
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		/*
+		 * In case when relation has inherited children, we try to apply
+		 * row-level security policy of them if configured.
+		 * In addition to regular replacement with a sub-query, we need
+		 * to adjust rtindex of AppendRelInfo and varno of translated_vars.
+		 * It makes sub-queries perform like regular relations being
+		 * inherited from a particular parent relation. So, a table scan
+		 * may have underlying a relation scan and two sub-query scans for
+		 * instance. If it is result relation of UPDATE or DELETE command,
+		 * rtindex to the original relation (regular relation) has to be
+		 * kept because sub-query cannot perform as an updatable relation.
+		 * So, we save it on child_result of AppendRelInfo; that shall be
+		 * used to track relations to be modified at inheritance_planner().
+		 */
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				/*
+				 * Save the rtindex of actual relation to be modified,
+				 * if parent relation is result relation of this query.
+				 */
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				/* Adjust varno to reference pseudo columns */
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var	   *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * It walks on the given join-tree to replace relations with row-level
+ * security policy by a simple sub-query.
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying pseudo column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 0b13645..2f56211 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3282,3 +3282,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 1e1e616..68be040 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f133e5f..1c97bd9 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -272,6 +272,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index f9afdb6..9ed3796 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3a918bb..c8ce32a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -113,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 74d0e12..252e45f 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1438,6 +1438,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index e4027bd..7bd334c 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
-- 
1.8.3.1

  [text/x-patch] 0008-RLS-Enforce-row-security-on-COPY.patch (4.8K, ../../52E1CC04.8040907@2ndquadrant.com/9-0008-RLS-Enforce-row-security-on-COPY.patch)
  download | inline diff:
From a599392b76b77aae5610b5654120f08ff19a6a92 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:50:23 +0800
Subject: [PATCH 08/10] RLS: Enforce row-security on COPY

---
 src/backend/commands/copy.c | 90 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f91f164..8e98fd2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
-- 
1.8.3.1

  [text/x-patch] 0009-RLS-Regression-tests.patch (43.2K, ../../52E1CC04.8040907@2ndquadrant.com/10-0009-RLS-Regression-tests.patch)
  download | inline diff:
From 3afd241dd3a81b64c42a86fbc8a87f997e080397 Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 12:58:29 +0800
Subject: [PATCH 09/10] RLS: Regression tests

---
 src/test/regress/expected/rowsecurity.out | 952 ++++++++++++++++++++++++++++++
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/serial_schedule          |   1 +
 src/test/regress/sql/rowsecurity.sql      | 298 ++++++++++
 4 files changed, 1252 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/rowsecurity.out
 create mode 100644 src/test/regress/sql/rowsecurity.sql

diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..44b2888
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,952 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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 TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/parallel_schedule b/src/test/regress/parallel_schedule
index 5758b07..f7963fc 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock replica_identity
+test: privileges rowsecurity security_label collate matview lock replica_identity
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 78348f5..51c4c12 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -94,6 +94,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;
-- 
1.8.3.1

  [text/x-patch] 0010-RLS-Supplimental-docs.patch (7.0K, ../../52E1CC04.8040907@2ndquadrant.com/11-0010-RLS-Supplimental-docs.patch)
  download | inline diff:
From bc5a54a13efff6b8deaa433f51578410681fed9d Mon Sep 17 00:00:00 2001
From: Craig Ringer <craig@2ndquadrant.com>
Date: Mon, 20 Jan 2014 13:50:37 +0800
Subject: [PATCH 10/10] RLS: Supplimental docs

---
 doc/src/sgml/user-manag.sgml | 145 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 145 insertions(+)

diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c764189 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.4 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
-- 
1.8.3.1

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
  2014-01-20 01:58                                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-20 06:15                                                                             ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2014-01-24 02:12                                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2014-01-24 03:15                                                                                 ` Craig Ringer <craig@2ndquadrant.com>
  0 siblings, 0 replies; 165+ messages in thread

From: Craig Ringer @ 2014-01-24 03:15 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; Kohei KaiGai <kaigai@kaigai.gr.jp>; pgsql-hackers

On 01/24/2014 10:12 AM, Craig Ringer wrote:
> (Re-sending; I forgot to cc the list)
> 
> On 01/20/2014 02:15 PM, Craig Ringer wrote:
>> On 01/20/2014 09:58 AM, Craig Ringer wrote:
>>> As it is I'm spending today reworking the RLS patch on top of the new
>>> approach to updatable security barrier views.
>>
>> To get that rolling I've split the RLS patch up into chunks, so we can
>> argue about the catalogs, ALTER syntax, and the actual row-filtering
>> implementation separately ;-)
>>
>> It's currently on git@github.com:ringerc/postgres.git in the branch
>> rls-9.4-split, which is subject to rebasing. I'm still going through it
>> making sure each chunk at least compiles and preferably passes "make
>> check".
> 
> That branch is now pretty stable, and passes checks at every stage up to
> the new RLS regression tests. I've pushed a new version to branch
> rls-9.4-split. Further updates will rebase this branch.
> 
> The tag rls-9.4-split-v5 identifies this particular push, and won't get
> rebased away.

Pushed a new rebase to the main working branch, merging in the fixes I
made to KaiGai's patch last time.

Tagged rls-9.4-split-v6

I haven't bothered with a patchset for this one, I'll be replacing it
again soon. This is just for anybody following along.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
@ 2013-12-14 04:56                                                                       ` Craig Ringer <craig@2ndquadrant.com>
  1 sibling, 0 replies; 165+ messages in thread

From: Craig Ringer @ 2013-12-14 04:56 UTC (permalink / raw)
  To: Gregory Smith <gregsmithpgsql@gmail.com>; Greg Stark <stark@mit.edu>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; pgsql-hackers; jeff.mccormick@crunchydatasolutions.com

On 12/14/2013 11:24 AM, Gregory Smith wrote:
> 
> 
> Things I can already see to work on here are:
> 
> -Fix the regression tests
> -Catch up to master again

I've got much of that in the tree:

https://github.com/ringerc/postgres/tree/rls-9.4

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
  2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
@ 2013-11-11 21:39                                                                 ` Robert Haas <robertmhaas@gmail.com>
  3 siblings, 0 replies; 165+ messages in thread

From: Robert Haas @ 2013-11-11 21:39 UTC (permalink / raw)
  To: Craig Ringer <craig@2ndquadrant.com>; +Cc: Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, Nov 6, 2013 at 1:38 AM, Craig Ringer <craig@2ndquadrant.com> wrote:
> (a) Updatable views are implemented in the rewriter, not the planner.
> The rewriter is not re-run when plans are invalidated or when the
> session authorization changes, etc. This means that we can't simply omit
> the RLS predicate for superuser because the same rewritten parse tree
> might get used for both superuser and non-superuser queries.

My impression was that we had discussed this problem with respect to
some earlier version of the RLS patch and that the conclusion of that
discussion was that we needed to record in the cached plan whether the
plan was one which is sensitive to the user ID and, if so, avoid using
that plan with a different user ID.  I am murky on the details; I
believe the original discussion of this topic was a year or more ago.

> (b) Inheritance is a problem when RLS is done in the rewriter. As I
> understood it from Kohei KaiGai's description to me earlier, there was a
> strong preference on -hackers to enforce RLS predicates for child and
> parent tables completely independently.

Not to put a too fine a point on it, but I think that's a really bad
plan; and I don't remember any such discussion.

> That's how RLS currently works,
> but it might be hard to get the same effect when applying RLS in the
> rewriter. We'd need to solve that, or redefine RLS's behaviour so that
> the predicate on a parent table applies to any child tables too.
> Personally I'd prefer the latter.

Yes, let's please redefine it.  The goal here ought to be to make RLS
work as smoothly as possible with the rest of the system, not to
invent weird semantics that are both unlike what we do elsewhere - and
difficult to implement, to boot.  I thought the whole point of
implementing security barrier views was that read-side RLS would work
just the same way, not having randomly incompatible semantics.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 15:28                                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  3 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-04 15:28 UTC (permalink / raw)
  To: Tom Lane <tgl@sss.pgh.pa.us>; +Cc: Robert Haas <robertmhaas@gmail.com>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/4 Tom Lane <tgl@sss.pgh.pa.us>:
> Robert Haas <robertmhaas@gmail.com> writes:
>> On Wed, Sep 4, 2013 at 10:45 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> Well, the security-barrier view stuff did not present itself as a 100%
>>> solution.  But perhaps more to the point, it was conceptually simple to
>>> implement, ie don't flatten views if they have this bit set, and don't
>>> push down quals into such sub-selects unless they're marked leakproof.
>
>> Right.  IMHO, this new feature should be similarly simple: when an
>> unprivileged user references a table, treat that as a reference to a
>> leakproof view over the table, with the RLS qual injected into the
>> view.
>
> And for insert/update/delete, we do what exactly?
>
This patch does not care about insert, because it shall be done around the
place where we usually put before-row-insert; that is not related to planner.

Regarding to update/delete, this patch also enhanced to allow update or
delete mechanism allows to take a sub-query on top of the table scan plan.

So, its explain output shows as follows:

postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
                    QUERY PLAN
--------------------------------------------------
 Update on customer
   ->  Subquery Scan on customer
         ->  Seq Scan on customer customer_1
               Filter: ("current_user"() = uname)

You can see this update has Subquery plan instead of regular relation scan.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
  2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
@ 2013-09-04 15:58                                                   ` Tom Lane <tgl@sss.pgh.pa.us>
  3 siblings, 0 replies; 165+ messages in thread

From: Tom Lane @ 2013-09-04 15:58 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Robert Haas <robertmhaas@gmail.com>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:
> 2013/9/4 Tom Lane <tgl@sss.pgh.pa.us>:
>> And for insert/update/delete, we do what exactly?

> Regarding to update/delete, this patch also enhanced to allow update or
> delete mechanism allows to take a sub-query on top of the table scan plan.

> So, its explain output shows as follows:

> postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
>                     QUERY PLAN
> --------------------------------------------------
>  Update on customer
>    ->  Subquery Scan on customer
>          ->  Seq Scan on customer customer_1
>                Filter: ("current_user"() = uname)

> You can see this update has Subquery plan instead of regular relation scan.

Really?  That wasn't apparent from reading the patch.  (Have I mentioned
it's desperately underdocumented?  Aside from needing a lot more in-code
comments than it's got, it would benefit from having an overview section
added to optimizer/README explaining stuff at the level of this
discussion.)

I'm a bit surprised that setrefs.c doesn't eliminate the Subquery Scan as
being a no-op, given that no quals end up getting applied at that level.
You might look into why not, since if that plan node were eliminated at
the end, it'd fix any efficiency complaints about this approach.

			regards, tom lane


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-08-31 05:03                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-31 19:40                                         ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  1 sibling, 2 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-31 05:03 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/30 Josh Berkus <josh@agliodbs.com>:
> On 08/30/2013 03:05 AM, Kohei KaiGai wrote:
>
>>> Surely someone in the security community has discussed this?
>>>
>> Security community considers covert channel is a hard to solve problem;
>> nearly impossible to eliminate.
>> Let's see the summary in wikipedia:
>>   http://en.wikipedia.org/wiki/Covert_channel
>
> Well, in each of the cases covered in that article, the given technology
> (OSI, TCP, etc.) takes specific provisions to limit the ability of
> attackers to discover information via the covert channel.
>
> However, we have yet to talk about taking any such provisions with
> Postgres.  If we commit this patch, arguably we'll have a row-level
> security feature which only protects data from well-behaved users, which
> seems counterproductive.
>
The point we shouldn't forget is information leakage via covert-channel
has less grade of threat than leakage via main-channel, because of
much less bandwidth.
Security community also concludes it is not avoidable nature as long
as human can observe system behavior and estimate something, thus,
security evaluation criteria does not require eliminate covert-channels
or does not pay attention about covert-channels for the products that
is installed on the environment with basic robustness (that means,
non-military, regular enterprise usage).
I don't think PostgreSQL goes into military-grade secure database
system. If so, it has to sacrifice many thing (like, performance,
usability, nature of open source, ...) for security. It's not a fact.

> So, arguments in favor of this patch:
> a) it's as good as Oracle's security features, giving us "check-box
> compliance".
> b) it allows securing individual rows against attackers with limited
> technical knowledge or limited database access, and could be very
> hardened in combination with intelligent access control.

Even if attacker has enough knowledge, the ratio they can estimate
hidden values is very limited because of much less bandwidth of
covert channels.

> c) it is an improvement on techniques like Veil (is it)?
> d) we plan to continue improving it and closing covert channels, or
> limiting their bandwidth.
>
> Arguments against:
> m) covert channels are currently broad enough to make it trivially
> circumventable (are they?)
> n) overhead and code maintenance required is substantial
>
> So, determinative questions:
>
> 1) are the security mechanisms supplied by this patch superior in some
> way to approaches like Veil for multi-tenant applications?  (this is a
> serious question, as multi-tenant applications are far less concerned
> about covert channels)
>
Yes. This RLS implementation targets the environment that does not
have requirement for a particular bandwidth upperbound on covert-
channels. It allows to centralize the place where we have to care
about access control on main-channel, so it well fits multi-tenant
applications.

> 2) do we plan to reduce the accessibility of data via covert channels
> over successive releases?  How?
>
Less covert channels is better than massive, if we could close it with
reasonable cost; that means run-time performance, code complexity
and so on.
However, in general, it is impossible to eliminate anything in spite of
less degree of threats because of smaller bandwidth. So, I don't think
this is an issue to spent much efforts to solve.

> 3) will accepting this patch allow our users in the Government space to
> more freely adopt PostgreSQL?
>
Government has two spaces. Most of their environment requires similar
requirement as enterprise grade system doing. On the other hand,
military environment often requires upper-bound of covert channel,
as a story I introduce on upthread, but they are minority and have
budget to develop special purpose database system designed for
security with first priority.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-31 19:40                                         ` Stephen Frost <sfrost@snowman.net>
  2013-09-01 18:44                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 1 reply; 165+ messages in thread

From: Stephen Frost @ 2013-08-31 19:40 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

KaiGai,

* Kohei KaiGai (kaigai@kaigai.gr.jp) wrote:
> The point we shouldn't forget is information leakage via covert-channel
> has less grade of threat than leakage via main-channel, because of
> much less bandwidth.

While true, this argument can't be used to simply ignore any and all
covert channels.  There are covert channels which are *not* much less
bandwidth, and the Filtered Rows one is one of those- it's simply too
big to ignore.  There are likely other which are equally large and
until we clean those up our RLS implementation will be questioned by
our users.

This does not mean that we need to clean up all covert channels and
things which are clearly intractable don't need to be addressed (eg:
the unique constraint situation; we can't both guarantee uniqueness
and hide the existance of an entry).

> Security community also concludes it is not avoidable nature as long
> as human can observe system behavior and estimate something,

This particular case is actually beyond 'estimation'. 

> Even if attacker has enough knowledge, the ratio they can estimate
> hidden values is very limited because of much less bandwidth of
> covert channels.

You really need to back away from this argument in this case.  The
example shown isn't based on estimation and provides quite high
bandwidth because it can be run by a computer.  This argument can't
be used for every situation where information is leaked.

> However, in general, it is impossible to eliminate anything in spite of
> less degree of threats because of smaller bandwidth. So, I don't think
> this is an issue to spent much efforts to solve.

I don't see a lot of complexity required to fix this specific case.  A
great deal of effort will be involved in going through the rest of the
code and various options and working to eliminate other similar cases,
but that's a reality we need to deal with.  Hopefully the cases found
will not require a lot of additional code to deal with.  We will need to
be mindful of new code which adds leaks or changes behavior such that
RLS doesn't function properly (hopefully, sufficient regression tests
will help to address that as well).

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130831194013.GQ2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-31 19:40                                         ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
@ 2013-09-01 18:44                                           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-01 18:44 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/8/31 Stephen Frost <sfrost@snowman.net>:
> KaiGai,
>
> * Kohei KaiGai (kaigai@kaigai.gr.jp) wrote:
>> The point we shouldn't forget is information leakage via covert-channel
>> has less grade of threat than leakage via main-channel, because of
>> much less bandwidth.
>
> While true, this argument can't be used to simply ignore any and all
> covert channels.  There are covert channels which are *not* much less
> bandwidth, and the Filtered Rows one is one of those- it's simply too
> big to ignore.  There are likely other which are equally large and
> until we clean those up our RLS implementation will be questioned by
> our users.
>
> This does not mean that we need to clean up all covert channels and
> things which are clearly intractable don't need to be addressed (eg:
> the unique constraint situation; we can't both guarantee uniqueness
> and hide the existance of an entry).
>
>> Security community also concludes it is not avoidable nature as long
>> as human can observe system behavior and estimate something,
>
> This particular case is actually beyond 'estimation'.
>
>> Even if attacker has enough knowledge, the ratio they can estimate
>> hidden values is very limited because of much less bandwidth of
>> covert channels.
>
> You really need to back away from this argument in this case.  The
> example shown isn't based on estimation and provides quite high
> bandwidth because it can be run by a computer.  This argument can't
> be used for every situation where information is leaked.
>
>> However, in general, it is impossible to eliminate anything in spite of
>> less degree of threats because of smaller bandwidth. So, I don't think
>> this is an issue to spent much efforts to solve.
>
> I don't see a lot of complexity required to fix this specific case.  A
> great deal of effort will be involved in going through the rest of the
> code and various options and working to eliminate other similar cases,
> but that's a reality we need to deal with.  Hopefully the cases found
> will not require a lot of additional code to deal with.  We will need to
> be mindful of new code which adds leaks or changes behavior such that
> RLS doesn't function properly (hopefully, sufficient regression tests
> will help to address that as well).
>
You're saying that we need to fix up cover-channels with unignorable
threat degree, even though it does not mean elimination of all the
covert-channels. I almost agree and feel it a reasonable stance.

One thing we need to pay attention is, how large covert-channel is
we need to fix-up and how small is we can ignore.
The reason why I used the term of "estimation", even heuristic or
machinery way, is this covert-channel does not expose the raw
data on the display.
I (personally) used this criteria to decide whether the covert-channel
is ignorable, or not. Thus, I tackled leaky-function problem on interaction
with views for security purpose.

It seems to me a rough watermark, rather than my criteria above, is
necessary to determine which covert-channel is hit for us.

Anyway, I try to investigate the scenario that Korotkov pointed out.
It should be a common problem for views with security_barrier attribute,
not only RLS feature, because it is designed on a common infrastructure.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-09-01 18:05                                         ` Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:01                                           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-03 00:08                                           ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
  1 sibling, 3 replies; 165+ messages in thread

From: Josh Berkus @ 2013-09-01 18:05 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Kaigai,

>> However, we have yet to talk about taking any such provisions with
>> Postgres.  If we commit this patch, arguably we'll have a row-level
>> security feature which only protects data from well-behaved users, which
>> seems counterproductive.
>>
> The point we shouldn't forget is information leakage via covert-channel
> has less grade of threat than leakage via main-channel, because of
> much less bandwidth.

That's an astonishingly weak argument, because the bandwidth you're
talking about is still very high, as in *hundreds* or *thousands* of
values per minute.  It's one thing to make the bandwidth argument for
things like monitoring power draw, where the leakage is one character
per hour, but the covert channels we're talking about are several orders
of magnitude faster than that.

So, I repeat: if you continue to pursue the argument that the covert
channels identified aren't significant because of bandwidth, you will
doom this patch.  The valid arguments are only two:

a) clearly identified use case X doesn't care about covert channels for
reason Y, and will find RLS extremely useful.

b) we can't fix these covert channels now, but plan to work on them in
the future, and have ideas for how to approach them.

> Security community also concludes it is not avoidable nature as long
> as human can observe system behavior and estimate something, thus,
> security evaluation criteria does not require eliminate covert-channels
> or does not pay attention about covert-channels for the products that
> is installed on the environment with basic robustness (that means,
> non-military, regular enterprise usage).

To be completely blunt, the security community does not understand
databases.  At all.  I'd think if anything had become clear through the
course of work on SEPosgres, it would be that.

>> So, determinative questions:
>>
>> 1) are the security mechanisms supplied by this patch superior in some
>> way to approaches like Veil for multi-tenant applications?  (this is a
>> serious question, as multi-tenant applications are far less concerned
>> about covert channels)
>>
> Yes. This RLS implementation targets the environment that does not
> have requirement for a particular bandwidth upperbound on covert-
> channels. It allows to centralize the place where we have to care
> about access control on main-channel, so it well fits multi-tenant
> applications.

Again, please abandon your bandwidth arguments.  They are irrelevant to
whether or not this patch gets accepted.

So, please try again?

>> 3) will accepting this patch allow our users in the Government space to
>> more freely adopt PostgreSQL?
>>
> Government has two spaces. Most of their environment requires similar
> requirement as enterprise grade system doing. On the other hand,
> military environment often requires upper-bound of covert channel,
> as a story I introduce on upthread, but they are minority and have
> budget to develop special purpose database system designed for
> security with first priority.

I don't think I understood this answer.  What I'm asking is: will
government agencies be sigificantly more likely to adopt PostgreSQL if
we have RLS, in this form?  Or do we need  "military-grade" before it
makes a difference?

-- 
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-09-01 19:01                                           ` Stephen Frost <sfrost@snowman.net>
  2 siblings, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2013-09-01 19:01 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Josh,

* Josh Berkus (josh@agliodbs.com) wrote:
> That's an astonishingly weak argument, because the bandwidth you're
> talking about is still very high, as in *hundreds* or *thousands* of
> values per minute.  

I agree that, in this specific case, we should do something about it.

> It's one thing to make the bandwidth argument for
> things like monitoring power draw, where the leakage is one character
> per hour, but the covert channels we're talking about are several orders
> of magnitude faster than that.

That's actually not an accurate representation of the bandwidth
associated with power draw measurements, but even if I convinced you it
was in the hundreds or thousands of values per minute, I doubt you'd
change your mind regarding the Filtered Rows issue or claim that we
should fix the power draw measurement issue. :)

> So, I repeat: if you continue to pursue the argument that the covert
> channels identified aren't significant because of bandwidth, you will
> doom this patch.  The valid arguments are only two:

There is actually another argument, which I mentioned up-thread..  There
are cases where you can't guarantee all the promises which can be asked
for.  In the unique-value case, we can't guarantee both that the values
are unique and that existing values can't be detected by an individual
with insert access.  We should make the user aware that allowing insert
access (or perhaps 'explain') opens up these cases.

> a) clearly identified use case X doesn't care about covert channels for
> reason Y, and will find RLS extremely useful.

These certainly exist and I'd argue that's part of why Veil exists..
Users of Veil (which I admit, I'm not) would likely be much happier
with an in-core solution because it will be much easier to use and
much more performant.

> b) we can't fix these covert channels now, but plan to work on them in
> the future, and have ideas for how to approach them.

I expect we will find more covert chanels which need to be fixed.

> To be completely blunt, the security community does not understand
> databases.  At all.  I'd think if anything had become clear through the
> course of work on SEPosgres, it would be that.

That's just false and it's poor of you to claim it.  The security
community is not one individual nor even some small group.  They're not
very obviously involved with PostgreSQL but that's no reason to claim
that they do not understand databases.

> I don't think I understood this answer.  What I'm asking is: will
> government agencies be sigificantly more likely to adopt PostgreSQL if
> we have RLS, in this form?  Or do we need  "military-grade" before it
> makes a difference?

From my experience, the answer would be 'yes' to your first question.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130901190142.GS2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-09-01 19:31                                           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2 siblings, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-01 19:31 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/1 Josh Berkus <josh@agliodbs.com>:
> Kaigai,
>
>>> However, we have yet to talk about taking any such provisions with
>>> Postgres.  If we commit this patch, arguably we'll have a row-level
>>> security feature which only protects data from well-behaved users, which
>>> seems counterproductive.
>>>
>> The point we shouldn't forget is information leakage via covert-channel
>> has less grade of threat than leakage via main-channel, because of
>> much less bandwidth.
>
> That's an astonishingly weak argument, because the bandwidth you're
> talking about is still very high, as in *hundreds* or *thousands* of
> values per minute.  It's one thing to make the bandwidth argument for
> things like monitoring power draw, where the leakage is one character
> per hour, but the covert channels we're talking about are several orders
> of magnitude faster than that.
>
> So, I repeat: if you continue to pursue the argument that the covert
> channels identified aren't significant because of bandwidth, you will
> doom this patch.  The valid arguments are only two:
>
> a) clearly identified use case X doesn't care about covert channels for
> reason Y, and will find RLS extremely useful.
>
> b) we can't fix these covert channels now, but plan to work on them in
> the future, and have ideas for how to approach them.
>
An important point is, what covert-channel should be closed, and what
other covert-channel can be ignored.
Even though the scenario Korotkov reported would be enough to make
us surprised, we may need to consider how wide covert channel should
be closed soon or later.

* A covert-channel that can expose raw-data directly, should be fixed up.
  => Thus, leaky-view problem needed to be fixed.

* A covert-channel that can expose existence of a particular value with
  less than Log(N) order trials to the possible range of hidden value.
  => Thus, this scenario needs to be cared?

Or, any other criteria even though?

My (current) preference is plan (c: we will be able to fix up *this*
cover-channel with reasonable efforts on explain code. probably,
we can close it if we don't print filtered rows under the sub-query
with security-barrier attribute.

It does not mean we shall or can fix up any possible covert-channels
being found in the future, however, I also agree that we shall tackle
covert-channel scenario being enough serious; that's criteria may
or may not be bandwidth of information leakage.

>> Security community also concludes it is not avoidable nature as long
>> as human can observe system behavior and estimate something, thus,
>> security evaluation criteria does not require eliminate covert-channels
>> or does not pay attention about covert-channels for the products that
>> is installed on the environment with basic robustness (that means,
>> non-military, regular enterprise usage).
>
> To be completely blunt, the security community does not understand
> databases.  At all.  I'd think if anything had become clear through the
> course of work on SEPosgres, it would be that.
>
>>> So, determinative questions:
>>>
>>> 1) are the security mechanisms supplied by this patch superior in some
>>> way to approaches like Veil for multi-tenant applications?  (this is a
>>> serious question, as multi-tenant applications are far less concerned
>>> about covert channels)
>>>
>> Yes. This RLS implementation targets the environment that does not
>> have requirement for a particular bandwidth upperbound on covert-
>> channels. It allows to centralize the place where we have to care
>> about access control on main-channel, so it well fits multi-tenant
>> applications.
>
> Again, please abandon your bandwidth arguments.  They are irrelevant to
> whether or not this patch gets accepted.
>
> So, please try again?
>
>>> 3) will accepting this patch allow our users in the Government space to
>>> more freely adopt PostgreSQL?
>>>
>> Government has two spaces. Most of their environment requires similar
>> requirement as enterprise grade system doing. On the other hand,
>> military environment often requires upper-bound of covert channel,
>> as a story I introduce on upthread, but they are minority and have
>> budget to develop special purpose database system designed for
>> security with first priority.
>
> I don't think I understood this answer.  What I'm asking is: will
> government agencies be sigificantly more likely to adopt PostgreSQL if
> we have RLS, in this form?  Or do we need  "military-grade" before it
> makes a difference?
>
Even though I'm not a marketer for public sector, not a small number
of Oracle virtual private database, that provide more simple functionality,
has been accepted for public sectors also, I believe it encourages
adoption of PostgreSQL on government agencies.
However, military division often requires special treatment for security
functionality but very small number of users are interested in, thus,
it is not a good idea to focus on this grade here.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-09-01 21:54                                             ` Greg Stark <stark@mit.edu>
  2013-09-02 03:47                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-09-04 15:10                                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 2 replies; 165+ messages in thread

From: Greg Stark @ 2013-09-01 21:54 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Sun, Sep 1, 2013 at 8:31 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
> Or, any other criteria even though?
>
> My (current) preference is plan (c: we will be able to fix up *this*
> cover-channel with reasonable efforts on explain code. probably,
> we can close it if we don't print filtered rows under the sub-query
> with security-barrier attribute.

I think the criteria being discussed in this thread are too strict.

It may be the case that Postgres cannot make a strong general case
that it protects against covert channels. However it may still be able
to make the much weaker case that it is *possible* to arrange your
database such that the covert channels are kept under control.

So I think up above Tom is wrong about why it's ok that INSERT leaks
keys when it reports a unique key violation. The reason why it's ok
that there's a covert channel there is that the DBA can avoid that
covert channel by being careful when creating unique constraints. He
or she should be aware that creating a unique constraint implicitly
provides a kind of limited access to data to users who have INSERT
privilege even if they lack the real SELECT privilege.

Likewise, as long as the covert channels in RLS are things the DBA has
even a modicum of control over I wouldn't be too worried. Afaict from
skimming the thread it looks like creating any indexes on columns
leaks what values of the index key exist in the table. Is it the case
that non-indexed columns do not get leaked?


-- 
greg


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
@ 2013-09-02 03:47                                               ` Greg Smith <greg@2ndQuadrant.com>
  2013-09-04 14:46                                                 ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
  1 sibling, 1 reply; 165+ messages in thread

From: Greg Smith @ 2013-09-02 03:47 UTC (permalink / raw)
  To: Greg Stark <stark@mit.edu>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; pgsql-hackers

On 9/1/13 5:54 PM, Greg Stark wrote:
> So I think up above Tom is wrong about why it's ok that INSERT leaks
> keys when it reports a unique key violation. The reason why it's ok
> that there's a covert channel there is that the DBA can avoid that
> covert channel by being careful when creating unique constraints. He
> or she should be aware that creating a unique constraint implicitly
> provides a kind of limited access to data to users who have INSERT
> privilege even if they lack the real SELECT privilege.

And if someone can INSERT values that they can't actually see once 
they're committed, that's a similarly bad we should describe.  People 
should be dumping their trash in their neighbor's yard.  I think 
eventually this needs to be wrestled to the ground in a robust way.  I 
want to see if all unique violations might be changed to give less 
information in this sort of RLS context.

One rough early idea is to create a new error condition that means you 
hit something protected by RLS, but doesn't leak any more information 
than that.  Just a generic "Security restriction operation" that comes 
out of fishing for keys, inserting outside your area, etc.  I want to 
think through some use cases and review the code to see whether that 
concept helps or not.

-- 
Greg Smith   2ndQuadrant US    greg@2ndQuadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support www.2ndQuadrant.com


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-09-02 03:47                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-09-04 14:46                                                 ` Robert Haas <robertmhaas@gmail.com>
  2013-09-04 15:01                                                   ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  0 siblings, 1 reply; 165+ messages in thread

From: Robert Haas @ 2013-09-04 14:46 UTC (permalink / raw)
  To: Greg Smith <greg@2ndquadrant.com>; +Cc: Greg Stark <stark@mit.edu>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; pgsql-hackers

On Sun, Sep 1, 2013 at 11:47 PM, Greg Smith <greg@2ndquadrant.com> wrote:
> And if someone can INSERT values that they can't actually see once they're
> committed, that's a similarly bad we should describe.

This is desirable in some cases but not others.  If the goal is
compartmentalization, then it's sensible to prevent this.  But you
might also have a "drop-box" environment - e.g. a student submits
coursework to a professor, and can't access the submitted work after
it's submitted.  FWIW, my CS classes in college had a tool that worked
just this way.

Or maybe an analyst writes a report and is then permitted to "give
away" the document to his boss for revisions.  Once the ownership of
the document has changed, the analyst can't see it any more, because
he can only see the documents he owns.  And maybe he's not permitted
to give away documents to just anyone (polluting their sandbox), but
he can give them to his boss (who expects to receive them).

The point is that we should be in the business of providing mechanism,
not policy.

-- 
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
  2013-09-02 03:47                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-09-04 14:46                                                 ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
@ 2013-09-04 15:01                                                   ` Stephen Frost <sfrost@snowman.net>
  0 siblings, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2013-09-04 15:01 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; +Cc: Greg Smith <greg@2ndquadrant.com>; Greg Stark <stark@mit.edu>; Kohei KaiGai <kaigai@kaigai.gr.jp>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; pgsql-hackers

* Robert Haas (robertmhaas@gmail.com) wrote:
> On Sun, Sep 1, 2013 at 11:47 PM, Greg Smith <greg@2ndquadrant.com> wrote:
> > And if someone can INSERT values that they can't actually see once they're
> > committed, that's a similarly bad we should describe.
> 
> This is desirable in some cases but not others.  If the goal is
> compartmentalization, then it's sensible to prevent this.  But you
> might also have a "drop-box" environment - e.g. a student submits
> coursework to a professor, and can't access the submitted work after
> it's submitted.  FWIW, my CS classes in college had a tool that worked
> just this way.

Agreed, and part of the discussion that I had w/ KaiGai and Simon was
that we should provide a way to let the user pick which they'd like.
This is the concept around 'insert privileges' being different from
'select privileges' wrt RLS.

> The point is that we should be in the business of providing mechanism,
> not policy.

++

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130904150119.GD2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
@ 2013-09-04 15:10                                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-04 15:10 UTC (permalink / raw)
  To: Greg Stark <stark@mit.edu>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/1 Greg Stark <stark@mit.edu>:
> On Sun, Sep 1, 2013 at 8:31 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:
>> Or, any other criteria even though?
>>
>> My (current) preference is plan (c: we will be able to fix up *this*
>> cover-channel with reasonable efforts on explain code. probably,
>> we can close it if we don't print filtered rows under the sub-query
>> with security-barrier attribute.
>
> I think the criteria being discussed in this thread are too strict.
>
> It may be the case that Postgres cannot make a strong general case
> that it protects against covert channels. However it may still be able
> to make the much weaker case that it is *possible* to arrange your
> database such that the covert channels are kept under control.
>
Yes. I have to admit it is difficult to determine a strict and regular rule
to handle covert-channel scenario.
Sorry, the later half of this sentence is uncertain for me.
Are you saying, even if we could have a strict rule, we may have many
possible covert channel for information leakage??

> So I think up above Tom is wrong about why it's ok that INSERT leaks
> keys when it reports a unique key violation. The reason why it's ok
> that there's a covert channel there is that the DBA can avoid that
> covert channel by being careful when creating unique constraints. He
> or she should be aware that creating a unique constraint implicitly
> provides a kind of limited access to data to users who have INSERT
> privilege even if they lack the real SELECT privilege.
>
IIRC, we discussed and concluded that the above information leakage
scenario shall be described in the documentation, and the way to
avoid valuable information leakage using alternative keys, a few years
before.

> Likewise, as long as the covert channels in RLS are things the DBA has
> even a modicum of control over I wouldn't be too worried. Afaict from
> skimming the thread it looks like creating any indexes on columns
> leaks what values of the index key exist in the table. Is it the case
> that non-indexed columns do not get leaked?
>
According to the scenario reported by Korotkov, he could find number
of rows being filtered by the given qualifier, thus it implies existence of
the row with a value in a particular range.
Its solution is that I noted above, and I'm working for it now.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
@ 2013-09-03 00:08                                           ` Bruce Momjian <bruce@momjian.us>
  2013-09-04 15:21                                             ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2 siblings, 1 reply; 165+ messages in thread

From: Bruce Momjian @ 2013-09-03 00:08 UTC (permalink / raw)
  To: Josh Berkus <josh@agliodbs.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Sun, Sep  1, 2013 at 11:05:58AM -0700, Josh Berkus wrote:
> > Security community also concludes it is not avoidable nature as long
> > as human can observe system behavior and estimate something, thus,
> > security evaluation criteria does not require eliminate covert-channels
> > or does not pay attention about covert-channels for the products that
> > is installed on the environment with basic robustness (that means,
> > non-military, regular enterprise usage).
> 
> To be completely blunt, the security community does not understand
> databases.  At all.  I'd think if anything had become clear through the
> course of work on SEPosgres, it would be that.

Agreed.  The security community realizes these covert channels exist,
but doesn't really have any recommendations on how to avoid them.  You
could argue that avoiding them is too tied to specific database
implementations, but there are general channels, like insert with a
unique key, that should at least have well-defined solutions.

-- 
  Bruce Momjian  <bruce@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + It's impossible for everything to be true. +


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
  2013-09-03 00:08                                           ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
@ 2013-09-04 15:21                                             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-04 15:21 UTC (permalink / raw)
  To: Bruce Momjian <bruce@momjian.us>; +Cc: Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

2013/9/3 Bruce Momjian <bruce@momjian.us>:
> On Sun, Sep  1, 2013 at 11:05:58AM -0700, Josh Berkus wrote:
>> > Security community also concludes it is not avoidable nature as long
>> > as human can observe system behavior and estimate something, thus,
>> > security evaluation criteria does not require eliminate covert-channels
>> > or does not pay attention about covert-channels for the products that
>> > is installed on the environment with basic robustness (that means,
>> > non-military, regular enterprise usage).
>>
>> To be completely blunt, the security community does not understand
>> databases.  At all.  I'd think if anything had become clear through the
>> course of work on SEPosgres, it would be that.
>
> Agreed.  The security community realizes these covert channels exist,
> but doesn't really have any recommendations on how to avoid them.  You
> could argue that avoiding them is too tied to specific database
> implementations, but there are general channels, like insert with a
> unique key, that should at least have well-defined solutions.
>
The security community also provides an extreme solution, but I don't
think it is suitable for flexible security policy and PostgreSQL wants it.

Their "extreme" solution manipulate definition of PK that automatically
become combined key that takes user-given key and security level being
set mandatory.
Thus, it does not conflict even if two different users with different security
level tries to insert a row with same primary key.
This technology is called polyinstantiation.
  http://en.wikipedia.org/wiki/Polyinstantiation

However, of course, I'm not favor to port this technology to PostgreSQL
world. Its side-effects are too much towards the problem to be solved.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
  2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
  2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-08-29 17:09                                 ` Stephen Frost <sfrost@snowman.net>
  1 sibling, 0 replies; 165+ messages in thread

From: Stephen Frost @ 2013-08-29 17:09 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

* Kohei KaiGai (kaigai@kaigai.gr.jp) wrote:
> Although I didn't touch this task by myself, my senior colleague said that we
> calculated some possible bandwidth of leakage as an evident when we ship
> supercomputer system with MAC feature for customer who worry about security.
> I'm not sure how to measure it. However, if we assume a human can run up to
> 5 query per seconds, he needs 2-3 seconds to identify a particular integer value
> less than 10000, it means bandwidth of this covert channel is less than 5bps.
> I'm not sure whether enterprise-grade dbms has to care about these amount
> of covert channel.

A human isn't necessary in this particular scenario- you're doing a
simple binary search through the space, which computers can be awful
good at.  Using the type's bounds (eg: an 'integer' field is only
32bits) and forcing index scans, you could tell how many records exist
and then break down how many exist in the first half, second half, and
then split those..  Eventually, you could work out every value in the
column.

That could be applied to variable length values also, though it'd be
more costly to get down to the exact values.

I don't have a solution to these issues offhand except to suggest that,
in such an environment, having a "don't allow these users to run
EXPLAIN" and similar options would probably be welcome.  Even then there
are potential timing attacks, but that certainly increases the level of
effort involved.

	Thanks,

		Stephen

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../20130829170959.GU2706@tamriel.snowman.net/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
  2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
@ 2013-09-01 12:41                           ` Craig Ringer <craig@2ndquadrant.com>
  2 siblings, 0 replies; 165+ messages in thread

From: Craig Ringer @ 2013-09-01 12:41 UTC (permalink / raw)
  To: Alexander Korotkov <aekorotkov@gmail.com>; +Cc: Kohei KaiGai <kaigai@kaigai.gr.jp>; Oleg Bartunov <obartunov@gmail.com>; Greg Smith <greg@2ndquadrant.com>; pgsql-hackers


> I think there is another "covert channel" much more serious than
> constrains. You can gather information about hidden data by reading
> query plans.

I think a lot of people would be quite happy to simply disallow EXPLAIN.
Define a permission for it, grant it to public and newly created
users/groups by default (for BC), and allow it to be revoked.

To define what is/isn't reasonable in terms of covert channel leakage, I
(reluctantly) suggest checking out the Common Criteria stuff. Yes, it's
verbose and questionably useful, but it's something that already exists
and that has lots of weight in government / large orgs.  I've started
reading into it but don't have enough info to comment on RLS
specifically yet.

Regarding unique keys and other constraints as leakage channels, I'm
inclined to think that this is partly a documentation issue. The docs
can explain what is/isn't protected against. Suggest creating keys
incorporating security domain identifiers so that users in different
security domains can't create conflicting values.

Possibly provide a mechanism to enforce that so that users can't attempt
to insert/update rows with a different security identifier. Or even make
it transparently part of the system's operations, an implicit extra
column in PRIMARY and FOREIGN keys on RLS-enabled tables.

What I'm more worried about re the covert uniqueness issue is that any
solution might run the risk of creating situations where changes in
access rights can make previously valid data in tables invalid. If two
users A and B can't see each other's data and create the same values for
a key, then B is given the rights to see A's data ... those unique
values now have duplicates.



So: I do think we need to step back a little when it comes to covert
channel attacks and define what we do/don't defend against.

What about timing attacks - do we need to make sure we don't leak
information about the number of RLS rows via scan durations? Make
everything constant-time? Yick. Crypto systems have a hard enough time
getting this stuff right, and I just think we say we don't defend
against timing attacks. If someone wants to mess around with adding
random sleeps in an executor hook, well, that's their pain to deal with.

-- 
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
@ 2013-08-28 11:56                     ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-07 01:23                       ` Re: [v9.4] row level security Peter Eisentraut <peter_e@gmx.net>
  1 sibling, 1 reply; 165+ messages in thread

From: Kohei KaiGai @ 2013-08-28 11:56 UTC (permalink / raw)
  To: Greg Smith <greg@2ndquadrant.com>; +Cc: pgsql-hackers

2013/8/27 Greg Smith <greg@2ndquadrant.com>:
> On 7/20/13 10:08 AM, Kohei KaiGai wrote:
>>
>> Hmm. I didn't have this idea. It seems to me fair enough and kills
>> necessity to enhance RangeTblEntry and getrelid() indeed.
>> I try to fix up this implementation according to your suggestion.
>
>
> How is that going?  I'm going to do a serious review of this myself over the
> next few weeks.  I have a good chunk of time set aside for it as part of a
> larger project.  I'm hoping to get more people here involved in that effort
> too, starting in the November CF if that works out.
>
Sorry, I tried to rework the portions that were not graceful so much, like
system column reference or update/delete on inherited tables, however,
it eventually came back to my original implementation. :-(

The attached patch fixed the portion I was pointed out, so its overall
design has not been changed so much.

> I've been trying to catch up with your larger plan for this feature for 9.4.
> You made this comment earlier:
>
>> Also, I'd like to have discussion for this feature in earlier half of
>> v9.4 to keep time for the remaining features, such as check on
>> writer-side, integration with selinux, and so on
>
> Is any of that code around yet?  I see that you have split your submissions
> so that a smaller program can be reviewed today.  I'd like to start taking a
> look at the next step too though.  For the project I'm starting to work on
> here, getting the integration with labeling also done is a very important
> thing to target for 9.4.  It would be nice to see how that fits together
> today, even if the code for it isn't being reviewed heavily yet.
>
The biggest (and most important) portion of overall picture is this patch;
that allows to restrict rows to be visible according to pre-configured
security policy per table.
Towards label based row-level security, it needs an infrastructure to
append before-row trigger on the fly, according to row-level security
configuration, because we need a check to prevent users to write
a record with unprivileged label. It is probably annoying requirement
for users to set up triggers for each table with security policy.
Then, I'd like to offer special functions of sepgsql that shall be used
to security policy function to filter out unprivileged tuples.
On the v9.4 time-frame, I'm not certain whether we can implement
facility to manage security label of user tables.
So, I assume label based row-level security is activated when
a special named text column is defined by users.

Towards v9.5, I'd like to have a feature to add hidden column for
security label purpose on table creation time without user's
consciousness.

> I don't quite understand yet what's missing on the writer side.  If you
> could help explain what's missing there, I would like to read about that.
>
It needs to back the discussion at CF-4th of v9.3. We discussed whether
we should have a special feature to check records to be inserted or
newer records to be updated, because before-row trigger can offer its
infrastructure, even though it requires users an extra configuration job
for each tables that have row-level security policy.
What I'd like to add on writer-side is an infrastructure for extensions to
inject before-row trigger functions on the tail of call-chain, that allows
to prevent records with violated values, and also allows sepgsql to
assign a default security label on new records being inserted.

Thanks,
-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] pgsql-v9.4-row-level-security.v4.patch (160.0K, ../../CADyhKSXQMvwZh7Hdx2stN9mwptXh=p2vg_t8F8yQ6XOAx+XZAQ@mail.gmail.com/2-pgsql-v9.4-row-level-security.v4.patch)
  download | inline diff:
 doc/src/sgml/catalogs.sgml                 |  65 ++
 doc/src/sgml/ref/alter_table.sgml          |  43 ++
 doc/src/sgml/user-manag.sgml               | 145 +++++
 src/backend/catalog/Makefile               |   4 +-
 src/backend/catalog/dependency.c           |   8 +
 src/backend/catalog/heap.c                 |   1 +
 src/backend/catalog/objectaddress.c        |  50 ++
 src/backend/catalog/pg_rowsecurity.c       | 337 ++++++++++
 src/backend/commands/copy.c                |  90 ++-
 src/backend/commands/event_trigger.c       |   1 +
 src/backend/commands/tablecmds.c           |  27 +
 src/backend/executor/execMain.c            |  27 +-
 src/backend/nodes/copyfuncs.c              |   3 +
 src/backend/nodes/equalfuncs.c             |   3 +
 src/backend/nodes/nodeFuncs.c              |  12 +-
 src/backend/nodes/outfuncs.c               |   3 +
 src/backend/nodes/readfuncs.c              |   2 +
 src/backend/optimizer/plan/planner.c       |  23 +-
 src/backend/optimizer/prep/preptlist.c     |  68 ++-
 src/backend/optimizer/prep/prepunion.c     |  95 ++-
 src/backend/optimizer/util/Makefile        |   2 +-
 src/backend/optimizer/util/rowsecurity.c   | 744 ++++++++++++++++++++++
 src/backend/parser/gram.y                  |  25 +
 src/backend/parser/parse_agg.c             |   6 +
 src/backend/parser/parse_expr.c            |   3 +
 src/backend/rewrite/rewriteHandler.c       |  16 +
 src/backend/utils/adt/ri_triggers.c        |  13 +-
 src/backend/utils/cache/plancache.c        |  32 +
 src/backend/utils/cache/relcache.c         |  17 +-
 src/bin/pg_dump/common.c                   |   4 +
 src/bin/pg_dump/pg_backup_archiver.c       |   1 +
 src/bin/pg_dump/pg_dump.c                  | 186 +++++-
 src/bin/pg_dump/pg_dump.h                  |  13 +-
 src/bin/pg_dump/pg_dump_sort.c             |   5 +
 src/bin/psql/describe.c                    |   7 +
 src/include/catalog/dependency.h           |   1 +
 src/include/catalog/indexing.h             |   5 +
 src/include/catalog/pg_class.h             |  24 +-
 src/include/catalog/pg_rowsecurity.h       |  76 +++
 src/include/miscadmin.h                    |   1 +
 src/include/nodes/execnodes.h              |   4 +
 src/include/nodes/nodeFuncs.h              |   1 +
 src/include/nodes/parsenodes.h             |  14 +-
 src/include/nodes/plannodes.h              |   2 +
 src/include/nodes/relation.h               |   6 +
 src/include/optimizer/rowsecurity.h        |  27 +
 src/include/parser/parse_node.h            |   3 +-
 src/include/rewrite/rewriteHandler.h       |   1 +
 src/include/utils/plancache.h              |   2 +
 src/include/utils/rel.h                    |   2 +
 src/test/regress/expected/rowsecurity.out  | 950 +++++++++++++++++++++++++++++
 src/test/regress/expected/sanity_check.out |   3 +-
 src/test/regress/parallel_schedule         |   2 +-
 src/test/regress/serial_schedule           |   1 +
 src/test/regress/sql/rowsecurity.sql       | 298 +++++++++
 55 files changed, 3454 insertions(+), 50 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 6715782..6f3c8fc 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -234,6 +234,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-rowlevelsec"><structname>pg_rowlevelsec</structname></link></entry>
+      <entry>row-level security policy of relation</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-seclabel"><structname>pg_seclabel</structname></link></entry>
       <entry>security labels on database objects</entry>
      </row>
@@ -1848,6 +1853,16 @@
      </row>
 
      <row>
+      <entry><structfield>relhasrowsecurity</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>
+       True if table has row-security policy; see
+       <link linkend="catalog-pg-rowsecurity"><structname>pg_rowsecurity</structname></link> catalog
+      </entry>
+     </row>
+
+     <row>
       <entry><structfield>relhassubclass</structfield></entry>
       <entry><type>bool</type></entry>
       <entry></entry>
@@ -5112,6 +5127,56 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-rowsecurity">
+  <title><structname>pg_rowlevelsec</structname></title>
+
+  <indexterm zone="catalog-pg-rowsecurity">
+   <primary>pg_rowsecurity</primary>
+  </indexterm>
+  <para>
+   The catalog <structname>pg_rowsecurity</structname> stores expression
+   tree of row-security policy to be performed on a particular relation.
+  </para>
+  <table>
+   <title><structname>pg_rowsecurity</structname> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>rsecrelid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.oid</literal></entry>
+      <entry>The table this row-security is for</entry>
+     </row>
+     <row>
+      <entry><structfield>rseccmd</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>The command this row-security is for. 'a' meaning all is only possible value right now.</entry>
+     </row>
+     <row>
+      <entry><structfield>rsecqual</structfield></entry>
+      <entry><type>pg_node_tree</type></entry>
+      <entry></entry>
+      <entry>An expression tree to be performed as rowl-security policy</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+  <note>
+   <para>
+    <literal>pg_class.relhasrowlevelsec</literal>
+    must be true if a table has row-level security policy in this catalog.
+   </para>
+  </note>
+ </sect1>
 
  <sect1 id="catalog-pg-seclabel">
   <title><structname>pg_seclabel</structname></title>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 2609d4a..b380852 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -69,12 +69,16 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
     NOT OF
     OWNER TO <replaceable class="PARAMETER">new_owner</replaceable>
     SET TABLESPACE <replaceable class="PARAMETER">new_tablespace</replaceable>
+    SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)
+    RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable>
 
 <phrase>and <replaceable class="PARAMETER">table_constraint_using_index</replaceable> is:</phrase>
 
     [ CONSTRAINT <replaceable class="PARAMETER">constraint_name</replaceable> ]
     { UNIQUE | PRIMARY KEY } USING INDEX <replaceable class="PARAMETER">index_name</replaceable>
     [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
+<phrase>and <replaceable class="PARAMETER">rowsec_command</replaceable> is:</phrase>
+    { ALL | SELECT | INSERT | UPDATE | DELETE }
 </synopsis>
  </refsynopsisdiv>
 
@@ -580,6 +584,31 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>SET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable> TO (<replaceable class="PARAMETER">condition</replaceable>)</literal></term>
+    <listitem>
+     <para>
+      This form set row-level security policy of the table.
+      Supplied <replaceable class="PARAMETER">condition</replaceable> performs
+      as if it is implicitly appended to the qualifiers of <literal>WHERE</literal>
+      clause, although mechanism guarantees to evaluate this condition earlier
+      than any other user given condition.
+      <literal>ALL</> is the only supported command type right now.
+      See also <xref linkend="ROW-SECURITY">.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESET ROW SECURITY FOR <replaceable class="PARAMETER">rowsec_command</replaceable></literal></term>
+    <listitem>
+     <para>
+      This form reset row-level security policy of the table, if exists.
+      <literal>ALL</> is the only supported command type right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>RENAME</literal></term>
     <listitem>
      <para>
@@ -821,6 +850,20 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable>
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="PARAMETER">condition</replaceable></term>
+      <listitem>
+       <para>
+        An expression that returns a value of type boolean. Expect for a case
+        when queries are executed with superuser privilege, only rows for which
+        this expression returns true will be fetched, updated or deleted.
+        This expression can reference columns of the relation being configured.
+        Sub-queries can be contained within expression tree, unless referenced
+        relation recursively references the same relation.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 177ac7a..c764189 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -439,4 +439,149 @@ DROP ROLE <replaceable>name</replaceable>;
   </para>
  </sect1>
 
+ <sect1 id="row-security">
+  <title>Row-security</title>
+  <para>
+   <productname>PostgreSQL</productname> v9.4 or later provides
+   row-security feature, like several commercial database
+   management system. It allows table owner to assign a particular
+   condition that performs as a security policy of the table; only
+   rows that satisfies the condition should be visible, except for
+   a case when superuser runs queries.
+  </para>
+  <para>
+   Row-security policy can be set using <literal>SET ROW SECURITY</literal>
+   command of <xref linkend="SQL-ALTERTABLE"> statement, as an expression
+   form that returns a value of type boolean. This expression can contain
+   references to columns of the relation, so it enables to construct
+   arbitrary rule to make access control decision based on contents of
+   each rows.
+  </para>
+  <para>
+   For example, the following <literal>customer</literal> table has
+   <literal>uname</literal> field to store user name, and it assume
+   we don't want to expose any properties of other customers.
+   The following command set <literal>current_user = uname</literal>
+   as row-security policy on the <literal>customer</literal> table.
+<screen>
+postgres=> ALTER TABLE customer SET ROW SECURITY
+                                FOR ALL TO (current_user = uname);
+ALTER TABLE
+</screen>
+   <xref linkend="SQL-EXPLAIN"> command shows how row-security policy
+   works on the supplied query.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM customer WHERE f_leak(upasswd);
+                 QUERY PLAN
+--------------------------------------------
+ Subquery Scan on customer
+   Filter: f_leak(customer.upasswd)
+   ->  Seq Scan on customer customer_1
+         Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+   This query execution plan means the preconfigured row-security policy is
+   implicitly added, and scan plan on the target relation being wrapped up
+   with a sub-query.
+   It ensures user given qualifiers, including functions with side effects,
+   are never executed earlier than the row-security policy regardless of
+   its cost, except for the cases when these were fully leakproof.
+   This design helps to tackle the scenario described in
+   <xref linkend="RULES-PRIVILEGES">; that introduces the order to evaluate
+   qualifiers is significant to keep confidentiality of invisible rows.
+  </para>
+
+  <para>
+   On the other hand, this design allows superusers to bypass checks with
+   row-security.
+   It ensures <application>pg_dump</application> can obtain a complete set
+   of database backup, and avoid to execute Trojan horse trap, being injected
+   as a row-security policy of user-defined table, with privileges of
+   superuser.
+  </para>
+
+  <para>
+   In case of queries on inherited tables, row-security policy of the parent
+   relation is not applied to child relations. Scope of the row-security
+   policy is limited to the relation on which it is set.
+<screen>
+postgres=> EXPLAIN(costs off) SELECT * FROM t1 WHERE f_leak(y);
+             QUERY PLAN
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.y)
+         ->  Seq Scan on t1 t1_1
+               Filter: ((x % 2) = 0)
+   ->  Seq Scan on t2
+         Filter: f_leak(y)
+   ->  Subquery Scan on t3
+         Filter: f_leak(t3.y)
+         ->  Seq Scan on t3 t3_1
+               Filter: ((x % 2) = 1)
+(11 rows)
+</screen>
+    In the above example, <literal>t1</literal> has inherited
+    child table <literal>t2</literal> and <literal>t3</literal>,
+    and row-security policy is set on only <literal>t1</literal>,
+    and <literal>t3</literal>, not <literal>t2</literal>.
+
+    The row-security policy of <literal>t1</literal>, <literal>x</literal>
+    must be even-number, is appended only <literal>t1</literal>,
+    neither <literal>t2</literal> nor <literal>t3</literal>.
+    On the contrary, <literal>t3</literal> has different row-security policy;
+    <literal>x</literal> must be odd-number.
+  </para>
+
+  <para>
+   Row-security feature also works to queries for writer-operations; such as
+   <xref linkend="SQL-INSERT">, <xref linkend="SQL-UPDATE"> or
+   <xref linkend="SQL-DELETE"> commands.
+   It ensures all the modified rows satisfies configured row-security policy.
+   The below query tries to update e-mail address of the
+   <literal>customer</> table, and configured row-security makes sure all the
+   modified rows's <literal>uname</> field has to match with
+   <literal>current_user</>.
+  </para>
+<screen>
+postgres=> EXPLAIN (costs off) UPDATE customer SET email = 'alice@example.com';
+                    QUERY PLAN
+--------------------------------------------------
+ Update on customer
+   ->  Subquery Scan on customer
+         ->  Seq Scan on customer customer_1
+               Filter: ("current_user"() = uname)
+(4 rows)
+</screen>
+  <para>
+   Modification of a certain table is consist of two different stuffs; one
+   is fetch rows to be modified from the result relation (except for
+   <literal>INSERT</> command), second is insertion of rows to the
+   result relation. Usually, before-row trigger or check constraints are
+   run in the second phase. In addition, row-security policy is also checked
+   on this stage, to prevent to insert or update rows with unprivileged
+   values.
+  </para>
+
+  <para>
+   Unlike other commercial database systems, we don't have any plan to allow
+   individual row-security policy for each command type. Even if we want to
+   perform with difference policy between <xref linkend="SQL-SELECT"> and
+   <xref linkend="SQL-DELETE">, <literal>RETURNING</> clause can leak the rows
+   to be invisible using <xref linkend="SQL-DELETE"> command.
+  </para>
+
+  <para>
+   Even though it is not a specific matter in row-security, please be careful
+   if you plan to use <literal>current_user</> in row-level security policy.
+   <xref linkend="SQL-DECLARE"> and <xref linkend="SQL-FETCH"> allows to
+   switch current user identifier during execution of the query.
+   Thus, it can fetch the first 100 rows with privilege of <literal>alice</>,
+   then remaining rows with privilege of <literal>bob</>. If and when query
+   execution plan contains some kind of materialization and row-security
+   policy contains <literal>current_user</>, the fetched tuples in
+   <literal>bob</>'s screen might be evaluated according to the privilege of
+   <literal>alice</>.
+  </para>
+ </sect1>
 </chapter>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index c4d3f3c..9b4e9f5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -15,7 +15,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
        pg_constraint.o pg_conversion.o \
        pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
        pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \
-       pg_type.o storage.o toasting.o
+       pg_rowsecurity.o pg_type.o storage.o toasting.o
 
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	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_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/dependency.c b/src/backend/catalog/dependency.c
index fe17c96..d13b2e1 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -45,6 +45,7 @@
 #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"
@@ -1249,6 +1250,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_ROWSECURITY:
+			RemoveRowSecurityById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2308,6 +2313,9 @@ getObjectClass(const ObjectAddress *object)
 
 		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
index 64ca312..7eb7a17 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -791,6 +791,7 @@ InsertPgClassTuple(Relation pg_class_desc,
 	values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey);
 	values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
 	values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
+	values[Anum_pg_class_relhasrowsecurity - 1] = BoolGetDatum(rd_rel->relhasrowsecurity);
 	values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass);
 	values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated);
 	values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 4d22f3a..b2aac6a 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2143,6 +2143,56 @@ getObjectDescription(const ObjectAddress *object)
 				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, "could not find tuple for row-security %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, "unexpected 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/pg_rowsecurity.c b/src/backend/catalog/pg_rowsecurity.c
new file mode 100644
index 0000000..34d33e8
--- /dev/null
+++ b/src/backend/catalog/pg_rowsecurity.c
@@ -0,0 +1,337 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_rowsecurity.c
+ *    routines to support manipulation of the pg_rowsecurity catalog
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "access/sysattr.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_rowsecurity.h"
+#include "catalog/pg_type.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_node.h"
+#include "parser/parse_relation.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/tqual.h"
+
+/*
+ * Load row-security policy from the catalog, and keep it on
+ * 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();
+	{
+		while (HeapTupleIsValid(tuple = systable_getnext(sscan)))
+		{
+			Datum	value;
+			bool	isnull;
+			char   *temp;
+
+			if (!rsdesc)
+			{
+				rscxt = AllocSetContextCreate(CacheMemoryContext,
+											  "Row-security descriptor",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+				oldcxt = MemoryContextSwitchTo(rscxt);
+				rsdesc = palloc0(sizeof(RowSecurityDesc));
+				rsdesc->rscxt = rscxt;
+				MemoryContextSwitchTo(oldcxt);
+			}
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rseccmd,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+
+			if (DatumGetChar(value) != ROWSECURITY_CMD_ALL)
+			{
+				ereport(WARNING,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("Per-command row-security not implemented")));
+				continue;
+			}
+
+			value = heap_getattr(tuple, Anum_pg_rowsecurity_rsecqual,
+								 RelationGetDescr(catalog), &isnull);
+			Assert(!isnull);
+			temp = TextDatumGetCString(value);
+
+			oldcxt = MemoryContextSwitchTo(rscxt);
+			rsdesc->rsall.rsecid = HeapTupleGetOid(tuple);
+			rsdesc->rsall.qual = (Expr *) stringToNode(temp);
+			Assert(exprType((Node *)rsdesc->rsall.qual) == BOOLOID);
+			rsdesc->rsall.hassublinks
+				= contain_subplans((Node *)rsdesc->rsall.qual);
+			MemoryContextSwitchTo(oldcxt);
+
+			pfree(temp);
+		}
+	}
+	PG_CATCH();
+	{
+		if (rscxt != NULL)
+			MemoryContextDelete(rscxt);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	systable_endscan(sscan);
+	heap_close(catalog, AccessShareLock);
+
+	relation->rsdesc = rsdesc;
+}
+
+/*
+ * Parse the supplied row-security policy, and insert/update a row
+ * of pg_rowsecurity catalog.
+ */
+static void
+InsertOrUpdatePolicyRow(Relation relation, char rseccmd, Node *clause)
+{
+	Oid				relationId = RelationGetRelid(relation);
+	Oid				rowsecId;
+	ParseState	   *pstate;
+	RangeTblEntry  *rte;
+	Node		   *qual;
+	Relation		catalog;
+	ScanKeyData		skeys[2];
+	SysScanDesc		sscan;
+	HeapTuple		oldtup;
+	HeapTuple		newtup;
+	Datum			values[Natts_pg_rowsecurity];
+	bool			isnull[Natts_pg_rowsecurity];
+	bool			replaces[Natts_pg_rowsecurity];
+	ObjectAddress	target;
+	ObjectAddress	myself;
+
+	/* Parse the supplied clause */
+	pstate = make_parsestate(NULL);
+
+	rte = addRangeTableEntryForRelation(pstate, relation,
+										NULL, false, false);
+	addRTEtoQuery(pstate, rte, false, true, true);
+
+	qual = transformWhereClause(pstate, copyObject(clause),
+								EXPR_KIND_ROW_SECURITY,
+								"ROW SECURITY");
+	/* zero-clear */
+	memset(values,   0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(isnull,   0, sizeof(isnull));
+
+	/* Update or Insert an entry to pg_rowsecurity catalog  */
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skeys[0],
+				Anum_pg_rowsecurity_rsecrelid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(RelationGetRelid(relation)));
+	ScanKeyInit(&skeys[1],
+				Anum_pg_rowsecurity_rseccmd,
+				BTEqualStrategyNumber, F_CHAREQ,
+				CharGetDatum(rseccmd));
+	sscan = systable_beginscan(catalog, RowSecurityRelidIndexId, true,
+							   NULL, 2, skeys);
+	oldtup = systable_getnext(sscan);
+	if (HeapTupleIsValid(oldtup))
+	{
+		rowsecId = HeapTupleGetOid(oldtup);
+
+		replaces[Anum_pg_rowsecurity_rsecqual - 1] = true;
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+
+		newtup = heap_modify_tuple(oldtup,
+								   RelationGetDescr(catalog),
+								   values, isnull, replaces);
+		simple_heap_update(catalog, &newtup->t_self, newtup);
+
+		deleteDependencyRecordsFor(RowSecurityRelationId, rowsecId, false);
+	}
+	else
+	{
+		values[Anum_pg_rowsecurity_rsecrelid - 1]
+			= ObjectIdGetDatum(relationId);
+		values[Anum_pg_rowsecurity_rseccmd - 1]
+			= CharGetDatum(rseccmd);
+		values[Anum_pg_rowsecurity_rsecqual - 1]
+			= CStringGetTextDatum(nodeToString(qual));
+		newtup = heap_form_tuple(RelationGetDescr(catalog),
+								 values, isnull);
+		rowsecId = simple_heap_insert(catalog, newtup);
+	}
+	CatalogUpdateIndexes(catalog, newtup);
+
+	heap_freetuple(newtup);
+
+	/* records dependencies of row-security policy and relation/columns */
+	target.classId = RelationRelationId;
+	target.objectId = relationId;
+	target.objectSubId = 0;
+
+	myself.classId = RowSecurityRelationId;
+	myself.objectId = rowsecId;
+	myself.objectSubId = 0;
+
+	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
+
+	recordDependencyOnExpr(&myself, qual, pstate->p_rtable,
+						   DEPENDENCY_NORMAL);
+	free_parsestate(pstate);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * Remove row-security policy row of pg_rowsecurity
+ */
+static void
+DeletePolicyRow(Relation relation, char rseccmd)
+{
+	Assert(rseccmd == ROWSECURITY_CMD_ALL);
+
+	if (relation->rsdesc)
+	{
+		ObjectAddress	address;
+
+		address.classId = RowSecurityRelationId;
+		address.objectId = relation->rsdesc->rsall.rsecid;
+		address.objectSubId = 0;
+
+		performDeletion(&address, DROP_RESTRICT, 0);
+	}
+	else
+	{
+		/* Nothing to do here */
+		elog(INFO, "relation %s has no row-security policy, skipped",
+			 RelationGetRelationName(relation));
+	}
+}
+
+/*
+ * Guts of row-security policy deletion.
+ */
+void
+RemoveRowSecurityById(Oid rowsecId)
+{
+	Relation	catalog;
+	ScanKeyData	skey;
+	SysScanDesc	sscan;
+	HeapTuple	tuple;
+	Relation	rel;
+	Oid			relid;
+
+	catalog = heap_open(RowSecurityRelationId, RowExclusiveLock);
+
+	ScanKeyInit(&skey,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(rowsecId));
+	sscan = systable_beginscan(catalog, RowSecurityOidIndexId, true,
+							   NULL, 1, &skey);
+	tuple = systable_getnext(sscan);
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "could not find tuple for row-security %u", rowsecId);
+
+	/*
+	 * Open and exclusive-lock the relation the row-security belongs to.
+	 */
+	relid = ((Form_pg_rowsecurity) GETSTRUCT(tuple))->rsecrelid;
+
+	rel = heap_open(relid, AccessExclusiveLock);
+
+	simple_heap_delete(catalog, &tuple->t_self);
+
+	/* Ensure relcache entries of other session being rebuilt */
+	CacheInvalidateRelcache(rel);
+
+	heap_close(rel, NoLock);
+
+	systable_endscan(sscan);
+	heap_close(catalog, RowExclusiveLock);
+}
+
+/*
+ * ALTER TABLE <name> SET ROW SECURITY (...) OR
+ *                    RESET ROW SECURITY
+ */
+void
+ATExecSetRowSecurity(Relation relation, const char *cmdname, Node *clause)
+{
+	Oid			relid = RelationGetRelid(relation);
+	char		rseccmd;
+
+	if (strcmp(cmdname, "all") == 0)
+		rseccmd = ROWSECURITY_CMD_ALL;
+	else
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("Row-security for \"%s\" is not implemented yet",
+						cmdname)));
+
+	if (clause != NULL)
+	{
+		InsertOrUpdatePolicyRow(relation, rseccmd, clause);
+
+		/*
+		 * Also, turn on relhasrowsecurity, if not.
+		 */
+		if (!RelationGetForm(relation)->relhasrowsecurity)
+		{
+			Relation	class_rel = heap_open(RelationRelationId,
+											  RowExclusiveLock);
+			HeapTuple	tuple;
+
+			tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", relid);
+
+			((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);
+		}
+	}
+	else
+		DeletePolicyRow(relation, rseccmd);
+
+	CacheInvalidateRelcache(relation);
+}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 31819cc..9cb8f1b 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -24,6 +24,7 @@
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/xact.h"
+#include "catalog/heap.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
@@ -34,15 +35,19 @@
 #include "libpq/pqformat.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
+#include "optimizer/rowsecurity.h"
 #include "parser/parse_relation.h"
+#include "parser/parsetree.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
 #include "tcop/tcopprot.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 #include "utils/memutils.h"
 #include "utils/portal.h"
 #include "utils/rel.h"
@@ -814,6 +819,21 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		rel = heap_openrv(stmt->relation,
 						  (is_from ? RowExclusiveLock : AccessShareLock));
 
+		tupDesc = RelationGetDescr(rel);
+		attnums = CopyGetAttnums(tupDesc, rel, stmt->attlist);
+
+		/*
+		 * We have to run regular query, if the target relation has
+ 		 * row-level security policy
+		 */
+		if (copy_row_security_policy((CopyStmt *)stmt, rel, attnums))
+		{
+			heap_close(rel, NoLock);	/* close with keeping lock */
+			relid = InvalidOid;
+			rel = NULL;
+		}
+		else
+		{
 		relid = RelationGetRelid(rel);
 
 		rte = makeNode(RangeTblEntry);
@@ -822,8 +842,6 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 		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) -
@@ -835,6 +853,7 @@ DoCopy(const CopyStmt *stmt, const char *queryString, uint64 *processed)
 				rte->selectedCols = bms_add_member(rte->selectedCols, attno);
 		}
 		ExecCheckRTPerms(list_make1(rte), true);
+		}
 	}
 	else
 	{
@@ -1193,6 +1212,53 @@ ProcessCopyOptions(CopyState cstate,
 }
 
 /*
+ * Adjust Query tree constructed with row-level security feature.
+ * If WITH OIDS option was supplied, it adds Var node to reference
+ * object-id system column.
+ */
+static void
+fixup_oid_of_rls_query(Query *query)
+{
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	Var			   *subvar;
+	ListCell	   *cell;
+	Form_pg_attribute	attform
+		= SystemAttributeDefinition(ObjectIdAttributeNumber, true);
+
+	subrte = rt_fetch((Index) 1, query->rtable);
+	Assert(subrte->rtekind == RTE_RELATION);
+
+	if (!SearchSysCacheExists2(ATTNUM,
+							   ObjectIdGetDatum(subrte->relid),
+							   Int16GetDatum(attform->attnum)))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("table \"%s\" does not have OIDs",
+						get_rel_name(subrte->relid))));
+
+	subvar = makeVar((Index) 1,
+					 attform->attnum,
+					 attform->atttypid,
+					 attform->atttypmod,
+					 attform->attcollation,
+					 0);
+	subtle = makeTargetEntry((Expr *) subvar,
+							 0,
+							 pstrdup(NameStr(attform->attname)),
+							 false);
+
+	query->targetList = list_concat(list_make1(subtle),
+									query->targetList);
+	/* adjust resno of TargetEntry */
+	foreach (cell, query->targetList)
+	{
+		subtle = lfirst(cell);
+		subtle->resno++;
+	}
+}
+
+/*
  * Common setup routines used by BeginCopyFrom and BeginCopyTo.
  *
  * Iff <binary>, unload or reload in the binary format, as opposed to the
@@ -1264,6 +1330,25 @@ BeginCopy(bool is_from,
 		Assert(!is_from);
 		cstate->rel = NULL;
 
+		/*
+		 * In case when regular COPY TO was replaced because of row-level
+		 * security, "raw_query" node have already analyzed / rewritten
+		 * query tree.
+		 */
+		if (IsA(raw_query, Query))
+		{
+			query = (Query *) raw_query;
+
+			Assert(query->querySource == QSRC_ROW_SECURITY);
+			if (cstate->oids)
+			{
+				fixup_oid_of_rls_query(query);
+				cstate->oids = false;
+			}
+			attnamelist = NIL;
+		}
+		else
+		{
 		/* Don't allow COPY w/ OIDs from a select */
 		if (cstate->oids)
 			ereport(ERROR,
@@ -1288,6 +1373,7 @@ BeginCopy(bool is_from,
 			elog(ERROR, "unexpected rewrite result");
 
 		query = (Query *) linitial(rewritten);
+		}
 
 		/* The grammar allows SELECT INTO, but we don't support that */
 		if (query->utilityStmt != NULL &&
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 328e2a8..0374629 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -992,6 +992,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_USER_MAPPING:
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
+		case OCLASS_ROWSECURITY:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index adc74dd..0f9efa0 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -37,6 +37,7 @@
 #include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_opclass.h"
+#include "catalog/pg_rowsecurity.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
@@ -2787,6 +2788,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetTableSpace:		/* must rewrite heap */
 			case AT_DropNotNull:		/* may change some SQL plans */
 			case AT_SetNotNull:
+			case AT_SetRowSecurity:
+			case AT_ResetRowSecurity:
 			case AT_GenericOptions:
 			case AT_AlterColumnGenericOptions:
 				cmd_lockmode = AccessExclusiveLock;
@@ -3155,6 +3158,8 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 		case AT_DropInherit:	/* NO INHERIT */
 		case AT_AddOf:			/* OF */
 		case AT_DropOf: /* NOT OF */
+		case AT_SetRowSecurity:
+		case AT_ResetRowSecurity:
 			ATSimplePermissions(rel, ATT_TABLE);
 			/* These commands never recurse */
 			/* No command-specific prep needed */
@@ -3440,6 +3445,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		case AT_DropOf:
 			ATExecDropOf(rel, lockmode);
 			break;
+		case AT_SetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, (Node *) cmd->def);
+			break;
+		case AT_ResetRowSecurity:
+			ATExecSetRowSecurity(rel, cmd->name, NULL);
+			break;
 		case AT_GenericOptions:
 			ATExecGenericOptions(rel, (List *) cmd->def);
 			break;
@@ -7789,6 +7800,22 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				Assert(defaultexpr);
 				break;
 
+			case OCLASS_ROWSECURITY:
+				/*
+				 * A row-level security policy can depend on a column in case
+				 * when the policy clause references a particular column.
+				 * Due to same reason why TRIGGER ... WHEN does not support
+				 * to change column's type being referenced in clause, row-
+				 * level security policy also does not support it.
+				 */
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						 errmsg("cannot alter type of a column used in a row-level security policy"),
+						 errdetail("%s depends on column \"%s\"",
+								   getObjectDescription(&foundObject),
+								   colName)));
+				break;
+
 			case OCLASS_PROC:
 			case OCLASS_TYPE:
 			case OCLASS_CAST:
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 791f336..619710d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -788,8 +788,9 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	foreach(l, plannedstmt->rowMarks)
 	{
 		PlanRowMark *rc = (PlanRowMark *) lfirst(l);
-		Oid			relid;
-		Relation	relation;
+		RangeTblEntry *rte = NULL;
+		Relation	relation = NULL;
+		LOCKMODE	lockmode = NoLock;
 		ExecRowMark *erm;
 
 		/* ignore "parent" rowmarks; they are irrelevant at runtime */
@@ -802,27 +803,33 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 			case ROW_MARK_NOKEYEXCLUSIVE:
 			case ROW_MARK_SHARE:
 			case ROW_MARK_KEYSHARE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, RowShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = RowShareLock;
 				break;
 			case ROW_MARK_REFERENCE:
-				relid = getrelid(rc->rti, rangeTable);
-				relation = heap_open(relid, AccessShareLock);
+				rte = rt_fetch(rc->rti, rangeTable);
+				lockmode = AccessShareLock;
 				break;
 			case ROW_MARK_COPY:
 				/* there's no real table here ... */
-				relation = NULL;
 				break;
 			default:
 				elog(ERROR, "unrecognized markType: %d", rc->markType);
-				relation = NULL;	/* keep compiler quiet */
 				break;
 		}
 
 		/* Check that relation is a legal target for marking */
-		if (relation)
+		if (rte)
+		{
+			if (rte->rtekind == RTE_RELATION)
+				relation = heap_open(rte->relid, lockmode);
+			else
+			{
+				Assert(rte->rtekind == RTE_SUBQUERY);
+				relation = heap_open(rte->rowsec_relid, lockmode);
+			}
 			CheckValidRowMarkRel(relation, rc->markType);
-
+		}
 		erm = (ExecRowMark *) palloc(sizeof(ExecRowMark));
 		erm->relation = relation;
 		erm->rti = rc->rti;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 788907e..a9c072c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1937,6 +1937,7 @@ _copyAppendRelInfo(const AppendRelInfo *from)
 
 	COPY_SCALAR_FIELD(parent_relid);
 	COPY_SCALAR_FIELD(child_relid);
+	COPY_SCALAR_FIELD(child_result);
 	COPY_SCALAR_FIELD(parent_reltype);
 	COPY_SCALAR_FIELD(child_reltype);
 	COPY_NODE_FIELD(translated_vars);
@@ -1978,6 +1979,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(relkind);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
+	COPY_SCALAR_FIELD(rowsec_relid);
 	COPY_SCALAR_FIELD(jointype);
 	COPY_NODE_FIELD(joinaliasvars);
 	COPY_NODE_FIELD(funcexpr);
@@ -2451,6 +2453,7 @@ _copyQuery(const Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
+	COPY_SCALAR_FIELD(sourceRelation);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 496e31d..ee69fdc 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -811,6 +811,7 @@ _equalAppendRelInfo(const AppendRelInfo *a, const AppendRelInfo *b)
 {
 	COMPARE_SCALAR_FIELD(parent_relid);
 	COMPARE_SCALAR_FIELD(child_relid);
+	COMPARE_SCALAR_FIELD(child_result);
 	COMPARE_SCALAR_FIELD(parent_reltype);
 	COMPARE_SCALAR_FIELD(child_reltype);
 	COMPARE_NODE_FIELD(translated_vars);
@@ -846,6 +847,7 @@ _equalQuery(const Query *a, const Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
+	COMPARE_SCALAR_FIELD(sourceRelation);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
@@ -2233,6 +2235,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	COMPARE_SCALAR_FIELD(relkind);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_SCALAR_FIELD(security_barrier);
+	COMPARE_SCALAR_FIELD(rowsec_relid);
 	COMPARE_SCALAR_FIELD(jointype);
 	COMPARE_NODE_FIELD(joinaliasvars);
 	COMPARE_NODE_FIELD(funcexpr);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 908f397..88ae823 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1936,8 +1936,11 @@ query_tree_walker(Query *query,
 		return true;
 	if (walker((Node *) query->withCheckOptions, context))
 		return true;
-	if (walker((Node *) query->returningList, context))
-		return true;
+	if (!(flags & QTW_IGNORE_RETURNING))
+	{
+		if (walker((Node *) query->returningList, context))
+			return true;
+	}
 	if (walker((Node *) query->jointree, context))
 		return true;
 	if (walker(query->setOperations, context))
@@ -2662,7 +2665,10 @@ query_tree_mutator(Query *query,
 
 	MUTATE(query->targetList, query->targetList, List *);
 	MUTATE(query->withCheckOptions, query->withCheckOptions, List *);
-	MUTATE(query->returningList, query->returningList, List *);
+	if (!(flags & QTW_IGNORE_RETURNING))
+		MUTATE(query->returningList, query->returningList, List *);
+	else
+		query->returningList = copyObject(query->returningList);
 	MUTATE(query->jointree, query->jointree, FromExpr *);
 	MUTATE(query->setOperations, query->setOperations, Node *);
 	MUTATE(query->havingQual, query->havingQual, Node *);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cff4734..53632c1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1925,6 +1925,7 @@ _outAppendRelInfo(StringInfo str, const AppendRelInfo *node)
 
 	WRITE_UINT_FIELD(parent_relid);
 	WRITE_UINT_FIELD(child_relid);
+	WRITE_UINT_FIELD(child_result);
 	WRITE_OID_FIELD(parent_reltype);
 	WRITE_OID_FIELD(child_reltype);
 	WRITE_NODE_FIELD(translated_vars);
@@ -2239,6 +2240,7 @@ _outQuery(StringInfo str, const Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
+	WRITE_INT_FIELD(sourceRelation);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
@@ -2374,6 +2376,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
 			WRITE_BOOL_FIELD(security_barrier);
+			WRITE_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			WRITE_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index aad63e5..5d9a9f9 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -199,6 +199,7 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
+	READ_INT_FIELD(sourceRelation);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
@@ -1213,6 +1214,7 @@ _readRangeTblEntry(void)
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
 			READ_BOOL_FIELD(security_barrier);
+			READ_OID_FIELD(rowsec_relid);
 			break;
 		case RTE_JOIN:
 			READ_ENUM_FIELD(jointype, JoinType);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d8aa35d..713f980 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -33,6 +33,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
@@ -177,6 +178,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	glob->lastPHId = 0;
 	glob->lastRowMarkId = 0;
 	glob->transientPlan = false;
+	glob->planUserId = InvalidOid;
 
 	/* Determine what fraction of the plan is likely to be scanned */
 	if (cursorOptions & CURSOR_OPT_FAST_PLAN)
@@ -254,6 +256,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->relationOids = glob->relationOids;
 	result->invalItems = glob->invalItems;
 	result->nParamExec = glob->nParamExec;
+	result->planUserId = glob->planUserId;
 
 	return result;
 }
@@ -404,6 +407,19 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	expand_inherited_tables(root);
 
 	/*
+	 * Apply row-security policy of the relation being referenced,
+	 * if configured with either of built-in or extension's features.
+	 * RangeTblEntry of the relation with row-security policy shall
+	 * be replaced with a row-security subquery that has simple scan
+	 * on the target relation with row-security policy qualifiers.
+	 *
+	 * This routine assumes PlannerInfo is already handled with
+	 * expand_inherited_tables, thus, AppendRelInfo or PlanRowMark
+	 * have valid information.
+	 */
+	apply_row_security_policy(root);
+
+	/*
 	 * Set hasHavingQual to remember if HAVING clause is present.  Needed
 	 * because preprocess_expression will reduce a constant-true condition to
 	 * an empty qual list ... but "HAVING TRUE" is not a semantic no-op.
@@ -888,6 +904,8 @@ inheritance_planner(PlannerInfo *root)
 					newrti = list_length(subroot.parse->rtable) + 1;
 					ChangeVarNodes((Node *) subroot.parse, rti, newrti, 0);
 					ChangeVarNodes((Node *) subroot.rowMarks, rti, newrti, 0);
+					if (subroot.parse->sourceRelation == rti)
+						subroot.parse->sourceRelation = newrti;
 					rte = copyObject(rte);
 					subroot.parse->rtable = lappend(subroot.parse->rtable,
 													rte);
@@ -951,7 +969,10 @@ inheritance_planner(PlannerInfo *root)
 		root->init_plans = subroot.init_plans;
 
 		/* Build list of target-relation RT indexes */
-		resultRelations = lappend_int(resultRelations, appinfo->child_relid);
+		resultRelations = lappend_int(resultRelations,
+									  (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid));
 
 		/* Build lists of per-relation WCO and RETURNING targetlists */
 		if (parse->withCheckOptions)
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index fb67f9e..0cccdf5 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -37,8 +37,49 @@
 
 
 static List *expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table);
+							   Index result_relation, Index source_relation,
+							   List *range_table);
 
+/*
+ * lookup_varattno
+ *
+ * This routine returns an attribute number to reference a particular
+ * attribute. In case when the target relation is really relation,
+ * we can reference arbitrary attribute (including system column)
+ * without any translations. However, we have to translate varattno
+ * of Var that references sub-queries being originated from regular
+ * relations with row-level security policy due to nature of sub-query
+ * that has no system-column.
+ */
+static AttrNumber
+lookup_varattno(AttrNumber attno, Index rt_index, List *rtables)
+{
+	RangeTblEntry  *rte = rt_fetch(rt_index, rtables);
+
+	if (rte->rtekind == RTE_SUBQUERY &&
+		rte->subquery->querySource == QSRC_ROW_SECURITY)
+	{
+		ListCell   *cell;
+
+		foreach (cell, rte->subquery->targetList)
+		{
+			TargetEntry *tle = lfirst(cell);
+			Var			*var;
+
+			if (IsA(tle->expr, Const))
+				continue;
+
+			var = (Var *) tle->expr;
+			Assert(IsA(var, Var));
+
+			if (var->varattno == attno)
+				return tle->resno;
+		}
+		elog(ERROR, "invalid attno %d on row-security subquery target-list",
+			 attno);
+	}
+	return attno;
+}
 
 /*
  * preprocess_targetlist
@@ -51,6 +92,7 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 {
 	Query	   *parse = root->parse;
 	int			result_relation = parse->resultRelation;
+	int			source_relation = parse->sourceRelation;
 	List	   *range_table = parse->rtable;
 	CmdType		command_type = parse->commandType;
 	ListCell   *lc;
@@ -73,8 +115,12 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 	 * 10/94
 	 */
 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+	{
 		tlist = expand_targetlist(tlist, command_type,
-								  result_relation, range_table);
+								  result_relation,
+								  source_relation,
+								  range_table);
+	}
 
 	/*
 	 * Add necessary junk columns for rowmarked rels.  These values are needed
@@ -96,7 +142,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 		{
 			/* It's a regular table, so fetch its TID */
 			var = makeVar(rc->rti,
-						  SelfItemPointerAttributeNumber,
+						  lookup_varattno(SelfItemPointerAttributeNumber,
+										  rc->rti, range_table),
 						  TIDOID,
 						  -1,
 						  InvalidOid,
@@ -112,7 +159,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
 			if (rc->isParent)
 			{
 				var = makeVar(rc->rti,
-							  TableOidAttributeNumber,
+							  lookup_varattno(TableOidAttributeNumber,
+											  rc->rti, range_table),
 							  OIDOID,
 							  -1,
 							  InvalidOid,
@@ -195,7 +243,8 @@ preprocess_targetlist(PlannerInfo *root, List *tlist)
  */
 static List *
 expand_targetlist(List *tlist, int command_type,
-				  Index result_relation, List *range_table)
+				  Index result_relation, Index source_relation,
+				  List *range_table)
 {
 	List	   *new_tlist = NIL;
 	ListCell   *tlist_item;
@@ -218,6 +267,9 @@ expand_targetlist(List *tlist, int command_type,
 
 	numattrs = RelationGetNumberOfAttributes(rel);
 
+	if (source_relation == 0)
+		source_relation = result_relation;
+
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
 		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno - 1];
@@ -298,8 +350,10 @@ expand_targetlist(List *tlist, int command_type,
 				case CMD_UPDATE:
 					if (!att_tup->attisdropped)
 					{
-						new_expr = (Node *) makeVar(result_relation,
-													attrno,
+						new_expr = (Node *) makeVar(source_relation,
+													lookup_varattno(attrno,
+														source_relation,
+														range_table),
 													atttype,
 													atttypmod,
 													attcollation,
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..017bc2c 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -55,6 +55,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	AppendRelInfo *appinfo;
+	bool		in_returning;
 } adjust_appendrel_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
@@ -1594,6 +1595,7 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 
 	context.root = root;
 	context.appinfo = appinfo;
+	context.in_returning = false;
 
 	/*
 	 * Must be prepared to start with a Query or a bare expression tree.
@@ -1605,10 +1607,29 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 		newnode = query_tree_mutator((Query *) node,
 									 adjust_appendrel_attrs_mutator,
 									 (void *) &context,
-									 QTW_IGNORE_RC_SUBQUERIES);
+									 QTW_IGNORE_RC_SUBQUERIES |
+									 QTW_IGNORE_RETURNING);
+		/*
+		 * Returning clause on the relation being replaced with row-
+		 * security subquery shall be handled in a special way, because
+		 * of no system columns on subquery.
+		 * Var references to system column or whole-row reference need
+		 * to be adjusted to reference pseudo columns on behalf of
+		 * the underlying these columns, however, RETURNGIN clause is
+		 * an exception because its Var nodes are evaluated towards
+		 * the "raw" target relation, not a fetched tuple.
+		 */
+		context.in_returning = true;
+		newnode->returningList = (List *)
+			expression_tree_mutator((Node *) newnode->returningList,
+									adjust_appendrel_attrs_mutator,
+									(void *) &context);
 		if (newnode->resultRelation == appinfo->parent_relid)
 		{
-			newnode->resultRelation = appinfo->child_relid;
+			newnode->resultRelation = (appinfo->child_result > 0 ?
+									   appinfo->child_result :
+									   appinfo->child_relid);
+			newnode->sourceRelation = appinfo->child_relid;
 			/* Fix tlist resnos too, if it's inherited UPDATE */
 			if (newnode->commandType == CMD_UPDATE)
 				newnode->targetList =
@@ -1624,6 +1645,49 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
 }
 
 static Node *
+fixup_var_on_rowsec_subquery(RangeTblEntry *rte, Var *var)
+{
+	ListCell   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+	/*
+	 * In case when row-level security policy is applied on the referenced
+	 * table, its RangeTblEntry (RTE_RELATION) is replaced with sub-query
+	 * to filter out unprivileged rows of underlying relation.
+	 * Even though reference to this sub-query should perform as if ones
+	 * to real relations, system column has to be cared in special way
+	 * due to the nature of sub-query.
+	 * Target-entries that reference system columns should be added on
+	 * rowlevelsec.c, so all we need to do here is looking up underlying
+	 * target-list that can reference underlying system column, and fix-
+	 * up varattno of the referencing Var node with resno of TargetEntry.
+	 */
+	foreach (cell, rte->subquery->targetList)
+	{
+		TargetEntry *subtle = lfirst(cell);
+
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *) subtle->expr;
+			Var	   *newnode;
+
+			if (subvar->varattno == var->varattno)
+			{
+				newnode = copyObject(var);
+				newnode->varattno = subtle->resno;
+				return (Node *)newnode;
+			}
+		}
+		else
+			Assert(IsA(subtle->expr, Const));
+	}
+	elog(ERROR, "could not find pseudo column of %d, relid %u",
+		 var->varattno, var->varno);
+	return NULL;
+}
+
+static Node *
 adjust_appendrel_attrs_mutator(Node *node,
 							   adjust_appendrel_attrs_context *context)
 {
@@ -1638,8 +1702,12 @@ adjust_appendrel_attrs_mutator(Node *node,
 		if (var->varlevelsup == 0 &&
 			var->varno == appinfo->parent_relid)
 		{
-			var->varno = appinfo->child_relid;
+			var->varno = (context->in_returning &&
+						  appinfo->child_result > 0 ?
+						  appinfo->child_result :
+						  appinfo->child_relid);
 			var->varnoold = appinfo->child_relid;
+
 			if (var->varattno > 0)
 			{
 				Node	   *newnode;
@@ -1664,6 +1732,14 @@ adjust_appendrel_attrs_mutator(Node *node,
 				 */
 				if (OidIsValid(appinfo->child_reltype))
 				{
+					Query          *parse = context->root->parse;
+					RangeTblEntry  *rte = rt_fetch(appinfo->child_relid,
+												   parse->rtable);
+					if (!context->in_returning &&
+						rte->rtekind == RTE_SUBQUERY &&
+						rte->subquery->querySource == QSRC_ROW_SECURITY)
+						var = (Var *)fixup_var_on_rowsec_subquery(rte, var);
+
 					Assert(var->vartype == appinfo->parent_reltype);
 					if (appinfo->parent_reltype != appinfo->child_reltype)
 					{
@@ -1708,7 +1784,18 @@ adjust_appendrel_attrs_mutator(Node *node,
 					return (Node *) rowexpr;
 				}
 			}
-			/* system attributes don't need any other translation */
+			else
+			{
+				Query		   *parse = context->root->parse;
+				RangeTblEntry  *rte;
+
+				rte = rt_fetch(appinfo->child_relid, parse->rtable);
+
+				if (!context->in_returning &&
+					rte->rtekind == RTE_SUBQUERY &&
+					rte->subquery->querySource == QSRC_ROW_SECURITY)
+					return fixup_var_on_rowsec_subquery(rte, var);
+			}
 		}
 		return (Node *) var;
 	}
diff --git a/src/backend/optimizer/util/Makefile b/src/backend/optimizer/util/Makefile
index 3b2d16b..3f5cb19 100644
--- a/src/backend/optimizer/util/Makefile
+++ b/src/backend/optimizer/util/Makefile
@@ -13,6 +13,6 @@ top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = clauses.o joininfo.o pathnode.o placeholder.o plancat.o predtest.o \
-       relnode.o restrictinfo.o tlist.o var.o
+       relnode.o restrictinfo.o tlist.o var.o rowsecurity.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/util/rowsecurity.c b/src/backend/optimizer/util/rowsecurity.c
new file mode 100644
index 0000000..c2e5a49
--- /dev/null
+++ b/src/backend/optimizer/util/rowsecurity.c
@@ -0,0 +1,744 @@
+/*
+ * optimizer/util/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/plannodes.h"
+#include "optimizer/clauses.h"
+#include "optimizer/prep.h"
+#include "optimizer/rowsecurity.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "tcop/utility.h"
+
+/* flags to pull row-security policy */
+#define RSEC_FLAG_HAS_SUBLINKS			0x0001
+
+/* hook to allow extensions to apply their own security policy */
+row_security_policy_hook_type	row_security_policy_hook = NULL;
+
+/*
+ * make_pseudo_column
+ *
+ * It makes a target-entry node that references underlying column.
+ * Its tle->expr is usualy Var node, but may be Const for dummy NULL
+ * if the supplied attribute was already dropped.
+ */
+static TargetEntry *
+make_pseudo_column(RangeTblEntry *subrte, AttrNumber attnum)
+{
+	Expr   *expr;
+	char   *resname;
+
+	Assert(subrte->rtekind == RTE_RELATION && OidIsValid(subrte->relid));
+	if (attnum == InvalidAttrNumber)
+	{
+		expr = (Expr *) makeWholeRowVar(subrte, (Index) 1, 0, false);
+		resname = get_rel_name(subrte->relid);
+	}
+	else
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(subrte->relid),
+								Int16GetDatum(attnum));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+				 attnum, subrte->relid);
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		if (attform->attisdropped)
+		{
+			char	namebuf[NAMEDATALEN];
+
+			/* Insert NULL just for a placeholder of dropped column */
+			expr = (Expr *) makeConst(INT4OID,
+									  -1,
+									  InvalidOid,
+									  sizeof(int32),
+									  (Datum) 0,
+									  true,		/* isnull */
+									  true);	/* byval */
+			sprintf(namebuf, "dummy-%d", (int)attform->attnum);
+			resname = pstrdup(namebuf);
+		}
+		else
+		{
+			expr = (Expr *) makeVar((Index) 1,
+									attform->attnum,
+									attform->atttypid,
+									attform->atttypmod,
+									attform->attcollation,
+									0);
+			resname = pstrdup(NameStr(attform->attname));
+		}
+		ReleaseSysCache(tuple);
+	}
+	return makeTargetEntry(expr, -1, resname, false);
+}
+
+/*
+ * lookup_pseudo_column
+ *
+ * It looks-up resource number of the target-entry relevant to the given
+ * Var-node that references the row-security subquery. If required column
+ * is not in the subquery's target-list, this function also adds new one
+ * and returns its resource number.
+ */
+static AttrNumber
+lookup_pseudo_column(PlannerInfo *root,
+					 RangeTblEntry *rte, AttrNumber varattno)
+{
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	TargetEntry	   *subtle;
+	ListCell	   *cell;
+
+	Assert(rte->rtekind == RTE_SUBQUERY &&
+		   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+	subqry = rte->subquery;
+	foreach (cell, subqry->targetList)
+	{
+		subtle = lfirst(cell);
+
+		/*
+		 * If referenced artifical column is already constructed on the
+		 * target-list of row-security subquery, nothing to do any more.
+		 */
+		if (IsA(subtle->expr, Var))
+		{
+			Var	   *subvar = (Var *)subtle->expr;
+
+			Assert(subvar->varno == 1);
+			if (subvar->varattno == varattno)
+				return subtle->resno;
+		}
+	}
+
+	/*
+	 * OK, we don't have an artifical column relevant to the required ones,
+	 * so let's create a new artifical column on demand.
+	 */
+	subrte = rt_fetch((Index) 1, subqry->rtable);
+	subtle = make_pseudo_column(subrte, varattno);
+	subtle->resno = list_length(subqry->targetList) + 1;
+
+	subqry->targetList = lappend(subqry->targetList, subtle);
+	rte->eref->colnames = lappend(rte->eref->colnames,
+								  makeString(pstrdup(subtle->resname)));
+	return subtle->resno;
+}
+
+/*
+ * fixup_varnode_walker
+ *
+ * It recursively fixes up references to the relation to be replaced by
+ * row-security sub-query, and adds pseudo columns relevant to the
+ * underlying system columns or whole row-reference on demand.
+ */
+typedef struct {
+	PlannerInfo	*root;
+	int		varlevelsup;
+	Index  *vartrans;
+} fixup_varnode_context;
+
+static bool
+fixup_varnode_walker(Node *node, fixup_varnode_context *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var))
+	{
+		Var			   *var = (Var *) node;
+		List		   *rtable = context->root->parse->rtable;
+		RangeTblEntry  *rte;
+		ListCell	   *cell;
+
+		/*
+		 * Ignore it, if Var node does not reference the Query currently
+		 * we focus on.
+		 */
+		if (var->varlevelsup != context->varlevelsup)
+			return false;
+
+		if (context->vartrans[var->varno] > 0)
+		{
+			Index	rtindex_trans = context->vartrans[var->varno];
+
+			rte = rt_fetch(rtindex_trans, rtable);
+			Assert(rte->rtekind == RTE_SUBQUERY &&
+				   rte->subquery->querySource == QSRC_ROW_SECURITY);
+
+			var->varno = var->varnoold = rtindex_trans;
+			var->varattno = lookup_pseudo_column(context->root, rte,
+													 var->varattno);
+		}
+		else
+		{
+			rte = rt_fetch(var->varno, rtable);
+			if (rte->rtekind == RTE_RELATION && rte->inh)
+			{
+				foreach (cell, context->root->append_rel_list)
+				{
+					AppendRelInfo  *appinfo = lfirst(cell);
+					RangeTblEntry  *child_rte;
+
+					if (appinfo->parent_relid != var->varno)
+						continue;
+
+					child_rte = rt_fetch(appinfo->child_relid, rtable);
+					if (child_rte->rtekind == RTE_SUBQUERY &&
+						child_rte->subquery->querySource == QSRC_ROW_SECURITY)
+						(void) lookup_pseudo_column(context->root,
+													child_rte,
+													var->varattno);
+				}
+			}
+		}
+	}
+	else if (IsA(node, RangeTblRef))
+	{
+		RangeTblRef  *rtr = (RangeTblRef *) node;
+
+		if (context->varlevelsup == 0 &&
+			context->vartrans[rtr->rtindex] != 0)
+			rtr->rtindex = context->vartrans[rtr->rtindex];
+	}
+	else if (IsA(node, Query))
+	{
+		bool	result;
+
+		context->varlevelsup++;
+		result = query_tree_walker((Query *) node,
+								   fixup_varnode_walker,
+								   (void *) context, 0);
+		context->varlevelsup--;
+
+		return result;
+	}
+	return expression_tree_walker(node,
+								  fixup_varnode_walker,
+								  (void *) context);
+}
+
+/*
+ * check_infinite_recursion
+ *
+ * It is a wrong row-security configuration, if we try to expand
+ * the relation inside of row-security subquery originated from
+ * same relation!
+ */
+static void
+check_infinite_recursion(PlannerInfo *root, Oid relid)
+{
+	PlannerInfo	   *parent = root->parent_root;
+
+	if (parent && parent->parse->querySource == QSRC_ROW_SECURITY)
+	{
+		RangeTblEntry  *rte = rt_fetch(1, parent->parse->rtable);
+
+		Assert(rte->rtekind == RTE_RELATION && OidIsValid(rte->relid));
+
+		if (relid == rte->relid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+					 errmsg("infinite recursion detected for relation \"%s\"",
+							get_rel_name(relid))));
+		check_infinite_recursion(parent, relid);
+	}
+}
+
+/*
+ * expand_rtentry_with_policy
+ *
+ * It extends a range-table entry of row-security sub-query with supplied
+ * security policy, and append it on the parse->rtable.
+ * This sub-query contains pseudo columns that reference underlying
+ * regular columns (at least, references to system column or whole of
+ * table reference shall be added on demand), and simple scan on the
+ * target relation.
+ * Any Var nodes that referenced the relation pointed by rtindex shall
+ * be adjusted to reference this sub-query instead. walker
+ */
+static Index
+expand_rtentry_with_policy(PlannerInfo *root, Index rtindex,
+						   Expr *qual, int flags)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Query		   *subqry;
+	RangeTblEntry  *subrte;
+	RangeTblRef	   *subrtr;
+	TargetEntry	   *subtle;
+	RangeTblEntry  *newrte;
+	HeapTuple		tuple;
+	AttrNumber		nattrs;
+	AttrNumber		attnum;
+	List		   *targetList = NIL;
+	List		   *colNameList = NIL;
+	PlanRowMark	   *rowmark;
+
+	Assert(rte->rtekind == RTE_RELATION && !rte->inh);
+
+	/* check recursion to prevent infinite loop */
+	check_infinite_recursion(root, rte->relid);
+
+	/* Expand views inside SubLink node */
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		QueryRewriteExpr((Node *)qual, list_make1_oid(rte->relid));
+
+	/*
+	 * Construction of sub-query
+	 */
+	subqry = (Query *) makeNode(Query);
+	subqry->commandType = CMD_SELECT;
+	subqry->querySource = QSRC_ROW_SECURITY;
+
+	subrte = copyObject(rte);
+	subqry->rtable = list_make1(subrte);
+
+	subrtr = makeNode(RangeTblRef);
+	subrtr->rtindex = 1;
+	subqry->jointree = makeFromExpr(list_make1(subrtr), (Node *) qual);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		subqry->hasSubLinks = true;
+
+	/*
+	 * Construction of TargetEntries that reference underlying columns.
+	 */
+	tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(rte->relid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for relation %u", rte->relid);
+	nattrs = ((Form_pg_class) GETSTRUCT(tuple))->relnatts;
+	ReleaseSysCache(tuple);
+
+	for (attnum = 1; attnum <= nattrs; attnum++)
+	{
+		subtle = make_pseudo_column(subrte, attnum);
+		subtle->resno = list_length(targetList) + 1;
+		Assert(subtle->resno == attnum);
+
+		targetList = lappend(targetList, subtle);
+		colNameList = lappend(colNameList,
+							  makeString(pstrdup(subtle->resname)));
+	}
+	subqry->targetList = targetList;
+
+	/* Expand RengeTblEntry with this sub-query */
+	newrte = makeNode(RangeTblEntry);
+	newrte->rtekind = RTE_SUBQUERY;
+	newrte->subquery = subqry;
+	newrte->security_barrier = true;
+	newrte->rowsec_relid = rte->relid;
+	newrte->eref = makeAlias(get_rel_name(rte->relid), colNameList);
+
+	parse->rtable = lappend(parse->rtable, newrte);
+
+	/*
+	 * Fix up PlanRowMark if needed, then add references to 'tableoid' and
+	 * 'ctid' that shall be added to handle row-level locking.
+	 * Also see preprocess_targetlist() that adds some junk attributes.
+	 */
+	rowmark = get_plan_rowmark(root->rowMarks, rtindex);
+	if (rowmark)
+	{
+		if (rowmark->rti == rowmark->prti)
+			rowmark->rti = rowmark->prti = list_length(parse->rtable);
+		else
+			rowmark->rti = list_length(parse->rtable);
+
+		lookup_pseudo_column(root, newrte, SelfItemPointerAttributeNumber);
+		lookup_pseudo_column(root, newrte, TableOidAttributeNumber);
+	}
+	return list_length(parse->rtable);
+}
+
+/*
+ * pull_row_security_policy
+ *
+ * It pulls the configured row-security policy of both built-in and
+ * extensions. If any, it returns expression tree.
+ */
+static Expr *
+pull_row_security_policy(CmdType cmd, Relation relation, int *p_flags)
+{
+	Expr   *quals = NULL;
+	int		flags = 0;
+
+	/*
+	 * Pull the row-security policy configured with built-in features,
+	 * if unprivileged users. Please note that superuser can bypass it.
+	 */
+	if (relation->rsdesc && !superuser())
+	{
+		RowSecurityDesc *rsdesc = relation->rsdesc;
+
+		quals = copyObject(rsdesc->rsall.qual);
+		if (rsdesc->rsall.hassublinks)
+			flags |= RSEC_FLAG_HAS_SUBLINKS;
+	}
+
+	/*
+	 * Also, ask extensions whether they want to apply their own
+	 * row-security policy. If both built-in and extension has
+	 * their own policy, it shall be merged.
+	 */
+	if (row_security_policy_hook)
+	{
+		List   *temp;
+
+		temp = (*row_security_policy_hook)(cmd, relation);
+		if (temp != NIL)
+		{
+			if ((flags & RSEC_FLAG_HAS_SUBLINKS) == 0 &&
+				contain_subplans((Node *) temp))
+				flags |= RSEC_FLAG_HAS_SUBLINKS;
+
+			if (quals != NULL)
+				temp = lappend(temp, quals);
+
+			if (list_length(temp) == 1)
+				quals = (Expr *)list_head(temp);
+			else if (list_length(temp) > 1)
+				quals = makeBoolExpr(AND_EXPR, temp, -1);
+		}
+	}
+	*p_flags = flags;
+	return quals;
+}
+
+/*
+ * copy_row_security_policy
+ *
+ * It construct a row-security subquery instead of raw COPY TO statement,
+ * if target relation has a row-level security policy
+ */
+bool
+copy_row_security_policy(CopyStmt *stmt, Relation rel, List *attnums)
+{
+	Expr		  *quals;
+	int			   flags;
+	Query		  *parse;
+	RangeTblEntry  *rte;
+	RangeTblRef	   *rtr;
+	TargetEntry	   *tle;
+	Var			   *var;
+	ListCell	   *cell;
+
+	if (stmt->is_from)
+		return false;
+
+	quals = pull_row_security_policy(CMD_SELECT, rel, &flags);
+	if (!quals)
+		return false;
+
+	parse = (Query *) makeNode(Query);
+	parse->commandType = CMD_SELECT;
+	parse->querySource = QSRC_ROW_SECURITY;
+
+	rte = makeNode(RangeTblEntry);
+	rte->rtekind = RTE_RELATION;
+	rte->relid = RelationGetRelid(rel);
+	rte->relkind = RelationGetForm(rel)->relkind;
+
+	foreach (cell, attnums)
+	{
+		HeapTuple	tuple;
+		Form_pg_attribute	attform;
+		AttrNumber	attno = lfirst_int(cell);
+
+		tuple = SearchSysCache2(ATTNUM,
+								ObjectIdGetDatum(RelationGetRelid(rel)),
+								Int16GetDatum(attno));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for attribute %d of relation %s",
+				 attno, RelationGetRelationName(rel));
+		attform = (Form_pg_attribute) GETSTRUCT(tuple);
+
+		var = makeVar((Index) 1,
+					  attform->attnum,
+					  attform->atttypid,
+					  attform->atttypmod,
+					  attform->attcollation,
+					  0);
+		tle = makeTargetEntry((Expr *) var,
+							  list_length(parse->targetList) + 1,
+							  pstrdup(NameStr(attform->attname)),
+							  false);
+		parse->targetList = lappend(parse->targetList, tle);
+
+		ReleaseSysCache(tuple);
+
+		rte->selectedCols = bms_add_member(rte->selectedCols,
+								attno - FirstLowInvalidHeapAttributeNumber);
+	}
+	rte->inFromCl = true;
+	rte->requiredPerms = ACL_SELECT;
+
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+
+	parse->jointree = makeFromExpr(list_make1(rtr), (Node *) quals);
+	parse->rtable = list_make1(rte);
+	if (flags & RSEC_FLAG_HAS_SUBLINKS)
+		parse->hasSubLinks = true;
+
+	stmt->query = (Node *) parse;
+
+	return true;
+}
+
+/*
+ * apply_row_security_relation
+ *
+ * It applies row-security policy on a particular relation being specified.
+ * If this relation is top of the inheritance tree, it also checks inherited
+ * children.
+ */
+static bool
+apply_row_security_relation(PlannerInfo *root, Index *vartrans,
+							CmdType cmd, Index rtindex)
+{
+	Query		   *parse = root->parse;
+	RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+	Relation		rel;
+	Expr		   *qual;
+	int				flags;
+	bool			result = false;
+
+	if (!rte->inh)
+	{
+		rel = heap_open(rte->relid, NoLock);
+		qual = pull_row_security_policy(cmd, rel, &flags);
+		if (qual)
+		{
+			vartrans[rtindex]
+				= expand_rtentry_with_policy(root, rtindex, qual, flags);
+			if (parse->resultRelation == rtindex)
+				parse->sourceRelation = vartrans[rtindex];
+			result = true;
+		}
+		heap_close(rel, NoLock);
+	}
+	else
+	{
+		/*
+		 * In case when relation has inherited children, we try to apply
+		 * row-level security policy of them if configured.
+		 * In addition to regular replacement with a sub-query, we need
+		 * to adjust rtindex of AppendRelInfo and varno of translated_vars.
+		 * It makes sub-queries perform like regular relations being
+		 * inherited from a particular parent relation. So, a table scan
+		 * may have underlying a relation scan and two sub-query scans for
+		 * instance. If it is result relation of UPDATE or DELETE command,
+		 * rtindex to the original relation (regular relation) has to be
+		 * kept because sub-query cannot perform as an updatable relation.
+		 * So, we save it on child_result of AppendRelInfo; that shall be
+		 * used to track relations to be modified at inheritance_planner().
+		 */
+		ListCell   *lc1, *lc2;
+
+		foreach (lc1, root->append_rel_list)
+		{
+			AppendRelInfo  *apinfo = lfirst(lc1);
+
+			if (apinfo->parent_relid != rtindex)
+				continue;
+
+			if (apply_row_security_relation(root, vartrans, cmd,
+											apinfo->child_relid))
+			{
+				/*
+				 * Save the rtindex of actual relation to be modified,
+				 * if parent relation is result relation of this query.
+				 */
+				if (parse->resultRelation == rtindex)
+					apinfo->child_result = apinfo->child_relid;
+
+				apinfo->child_relid = vartrans[apinfo->child_relid];
+				/* Adjust varno to reference pseudo columns */
+				foreach (lc2, apinfo->translated_vars)
+				{
+					Var	   *var = lfirst(lc2);
+
+					if (var)
+						var->varno = apinfo->child_relid;
+				}
+				result = true;
+			}
+		}
+	}
+	return result;
+}
+
+/*
+ * apply_row_security_recursive
+ *
+ * It walks on the given join-tree to replace relations with row-level
+ * security policy by a simple sub-query.
+ */
+static bool
+apply_row_security_recursive(PlannerInfo *root, Index *vartrans, Node *jtnode)
+{
+	bool	result = false;
+
+	if (jtnode == NULL)
+		return false;
+	if (IsA(jtnode, RangeTblRef))
+	{
+		Index			rtindex = ((RangeTblRef *) jtnode)->rtindex;
+		Query		   *parse = root->parse;
+		RangeTblEntry  *rte = rt_fetch(rtindex, parse->rtable);
+		CmdType			cmd;
+
+		/* Only relation can have row-security policy */
+		if (rte->rtekind != RTE_RELATION)
+			return false;
+
+		/*
+		 * Prevents infinite recursion. Please note that rtindex == 1
+		 * of the row-security subquery is a relation being already
+		 * processed on the upper level.
+		 */
+		if (parse->querySource == QSRC_ROW_SECURITY && rtindex == 1)
+			return false;
+
+		/* Is it a result relation of UPDATE or DELETE command? */
+		if (parse->resultRelation == rtindex)
+			cmd = parse->commandType;
+		else
+			cmd = CMD_SELECT;
+
+		/* Try to apply row-security policy, if configured */
+		result = apply_row_security_relation(root, vartrans, cmd, rtindex);
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *l;
+
+		foreach (l, f->fromlist)
+		{
+			if (apply_row_security_recursive(root, vartrans, lfirst(l)))
+				result = true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		if (apply_row_security_recursive(root, vartrans, j->larg))
+			result = true;
+		if (apply_row_security_recursive(root, vartrans, j->rarg))
+			result = true;
+	}
+	else
+		elog(ERROR, "unexpected node type: %d", (int) nodeTag(jtnode));
+
+	return result;
+}
+
+/*
+ * apply_row_security_policy
+ *
+ * Entrypoint to apply configured row-security policy of the relation.
+ *
+ * In case when the supplied query references relations with row-security
+ * policy, its RangeTblEntry shall be replaced by a row-security subquery
+ * that has simple scan on the referenced table with policy qualifiers.
+ * Of course, security-barrier shall be set on the subquery to prevent
+ * unexpected push-down of functions without leakproof flag.
+ *
+ * For example, when table t1 has a security policy "(x % 2 = 0)", the
+ * following query:
+ *   SELECT * FROM t1 WHERE f_leak(y)
+ * performs as if
+ *   SELECT * FROM (
+ *     SELECT x, y FROM t1 WHERE (x % 2 = 0)
+ *   ) AS t1 WHERE f_leak(y)
+ * would be given. Because the sub-query has security barrier flag, 
+ * configured security policy qualifier is always executed prior to
+ * user given functions.
+ */
+void
+apply_row_security_policy(PlannerInfo *root)
+{
+	Query	   *parse = root->parse;
+	Oid			curr_userid;
+	int			curr_seccxt;
+	Index	   *vartrans;
+
+	/*
+	 * Mode checks. In case when SECURITY_ROW_LEVEL_DISABLED is set,
+	 * no row-level security policy should be applied regardless
+	 * whether it is built-in or extension.
+	 */
+	GetUserIdAndSecContext(&curr_userid, &curr_seccxt);
+	if (curr_seccxt & SECURITY_ROW_LEVEL_DISABLED)
+		return;
+
+	vartrans = palloc0(sizeof(Index) * (list_length(parse->rtable) + 1));
+	if (apply_row_security_recursive(root, vartrans, (Node *)parse->jointree))
+	{
+		PlannerGlobal  *glob = root->glob;
+		PlanInvalItem  *pi_item;
+		fixup_varnode_context context;
+
+		/*
+		 * Constructed Plan with row-level security policy depends on
+		 * properties of current user (database superuser can bypass
+		 * configured row-security policy!), thus, it has to be
+		 * invalidated when its assumption was changed.
+		 */
+		if (!OidIsValid(glob->planUserId))
+		{
+			/* Plan invalidation on session user-id */
+			glob->planUserId = GetUserId();
+
+			/* Plan invalidation on catalog updates of pg_authid */
+			pi_item = makeNode(PlanInvalItem);
+			pi_item->cacheId = AUTHOID;
+			pi_item->hashValue =
+				GetSysCacheHashValue1(AUTHOID,
+									  ObjectIdGetDatum(glob->planUserId));
+			glob->invalItems = lappend(glob->invalItems, pi_item);
+		}
+		else
+			Assert(glob->planUserId == GetUserId());
+
+		/*
+		 * Var-nodes that referenced RangeTblEntry to be replaced by
+		 * row-security sub-query have to be adjusted for appropriate
+		 * reference to the underlying pseudo column of the relation.
+		 */
+		context.root = root;
+		context.varlevelsup = 0;
+		context.vartrans = vartrans;
+		query_tree_walker(parse,
+						  fixup_varnode_walker,
+						  (void *) &context,
+						  QTW_IGNORE_RETURNING);
+	}
+	pfree(vartrans);
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 22e82ba..592671a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -256,6 +256,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 %type <node>	alter_table_cmd alter_type_cmd opt_collate_clause
 %type <list>	alter_table_cmds alter_type_cmds
+%type <str>		row_security_cmd
 
 %type <dbehavior>	opt_drop_behavior
 
@@ -2173,6 +2174,24 @@ alter_table_cmd:
 					n->def = (Node *)$2;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> SET ROW SECURITY FOR <cmd> TO (<expr>) */
+			| SET ROW SECURITY FOR row_security_cmd TO '(' a_expr ')'
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_SetRowSecurity;
+					n->name = $5;
+					n->def = (Node *) $8;
+					$$ = (Node *)n;
+				}
+			/* ALTER TABLE <name> RESET ROW SECURITY FOR <cmd> */
+			| RESET ROW SECURITY FOR row_security_cmd
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_ResetRowSecurity;
+					n->name = $5;
+					n->def = NULL;
+					$$ = (Node *)n;
+				}
 			| alter_generic_options
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2244,6 +2263,12 @@ reloption_elem:
 				}
 		;
 
+row_security_cmd: ALL	{ $$ = "all"; }
+			| SELECT	{ $$ = "select"; }
+			| INSERT	{ $$ = "insert"; }
+			| UPDATE	{ $$ = "update"; }
+			| DELETE_P	{ $$ = "delete"; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 4e4e1cd..d9049e0 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -272,6 +272,9 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 		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
@@ -547,6 +550,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
 		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
index 68b711d..fb09c8c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1459,6 +1459,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		case EXPR_KIND_OFFSET:
 		case EXPR_KIND_RETURNING:
 		case EXPR_KIND_VALUES:
+		case EXPR_KIND_ROW_SECURITY:
 			/* okay */
 			break;
 		case EXPR_KIND_CHECK_CONSTRAINT:
@@ -2640,6 +2641,8 @@ ParseExprKindName(ParseExprKind exprKind)
 			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/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index c55fb14..218416a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3097,3 +3097,19 @@ QueryRewrite(Query *parsetree)
 
 	return results;
 }
+
+/*
+ * QueryRewriteExpr
+ *
+ * This routine provides an entry point of query rewriter towards
+ * a certain expression tree with SubLink node; being added after
+ * the top level query rewrite.
+ * It primarily intends to expand views appeared in the qualifiers
+ * appended with row-level security which needs to modify query
+ * tree at head of the planner stage.
+ */
+void
+QueryRewriteExpr(Node *node, List *activeRIRs)
+{
+	fireRIRonSubLink(node, activeRIRs);
+}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 65edc1f..b36da97 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -3008,6 +3008,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 	int			spi_result;
 	Oid			save_userid;
 	int			save_sec_context;
+	int			temp_sec_context;
 	Datum		vals[RI_MAX_NUMKEYS * 2];
 	char		nulls[RI_MAX_NUMKEYS * 2];
 
@@ -3087,8 +3088,18 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
 
 	/* Switch to proper UID to perform check as */
 	GetUserIdAndSecContext(&save_userid, &save_sec_context);
+
+	/*
+	 * Row-level security should be disabled in case when 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 (source_is_pk)
+		temp_sec_context |= SECURITY_ROW_LEVEL_DISABLED;
+
 	SetUserIdAndSecContext(RelationGetForm(query_rel)->relowner,
-						   save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+						   temp_sec_context);
 
 	/* Finally we can run the query. */
 	spi_result = SPI_execute_snapshot(qplan,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index cf740a9..f1e67a5 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -53,6 +53,7 @@
 #include "catalog/namespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
+#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/planmain.h"
@@ -795,6 +796,16 @@ CheckCachedPlan(CachedPlanSource *plansource)
 		AcquireExecutorLocks(plan->stmt_list, true);
 
 		/*
+		 * If plan was constructed with assumption of a particular user-id,
+		 * and it is different from the current one, the cached-plan shall
+		 * be invalidated to construct suitable query plan.
+		 */
+		if (plan->is_valid &&
+			OidIsValid(plan->planUserId) &&
+			plan->planUserId == GetUserId())
+			plan->is_valid = false;
+
+		/*
 		 * If plan was transient, check to see if TransactionXmin has
 		 * advanced, and if so invalidate it.
 		 */
@@ -847,6 +858,8 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 {
 	CachedPlan *plan;
 	List	   *plist;
+	ListCell   *cell;
+	Oid			planUserId = InvalidOid;
 	bool		snapshot_set;
 	bool		spi_pushed;
 	MemoryContext plan_context;
@@ -914,6 +927,24 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 		PopActiveSnapshot();
 
 	/*
+	 * Check whether the generated plan assumes a particular user-id, or not.
+	 * In case when a valid user-id is recorded on PlannedStmt->planUserId,
+	 * it should be kept and used to validation check of the cached plan
+	 * under the "current" user-id.
+	 */
+	foreach (cell, plist)
+	{
+		PlannedStmt	*pstmt = lfirst(cell);
+
+		if (IsA(pstmt, PlannedStmt) && OidIsValid(pstmt->planUserId))
+		{
+			Assert(!OidIsValid(planUserId) || planUserId == pstmt->planUserId);
+
+			planUserId = pstmt->planUserId;
+		}
+	}
+
+	/*
 	 * Normally we make a dedicated memory context for the CachedPlan and its
 	 * subsidiary data.  (It's probably not going to be large, but just in
 	 * case, use the default maxsize parameter.  It's transient for the
@@ -956,6 +987,7 @@ BuildCachedPlan(CachedPlanSource *plansource, List *qlist,
 	plan->is_oneshot = plansource->is_oneshot;
 	plan->is_saved = false;
 	plan->is_valid = true;
+	plan->planUserId = planUserId;
 
 	/* assign generation number to new plan */
 	plan->generation = ++(plansource->generation);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66fb63b..828c10e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -50,6 +50,7 @@
 #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"
@@ -935,6 +936,11 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 	else
 		relation->trigdesc = NULL;
 
+	if (relation->rd_rel->relhasrowsecurity)
+		RelationBuildRowSecurity(relation);
+	else
+		relation->rsdesc = NULL;
+
 	/*
 	 * if it's an index, initialize index-related information
 	 */
@@ -1842,6 +1848,8 @@ RelationDestroyRelation(Relation relation)
 		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);
@@ -3161,7 +3169,13 @@ RelationCacheInitializePhase3(void)
 				relation->rd_rel->relhastriggers = false;
 			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);
 
@@ -4407,6 +4421,7 @@ load_relcache_init_file(bool shared)
 		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/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 247ad92..9603ef4 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 		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
index 50619a2..c3cb9a0 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3125,6 +3125,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
 				 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
index e1ef55f..0786393 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -249,6 +249,7 @@ static char *myFormatType(const char *typname, int32 typmod);
 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);
@@ -2686,6 +2687,134 @@ dumpBlobs(Archive *fout, void *arg)
 	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_rseccmd;
+	int			i_rsecqual;
+	int			i, j, ntups;
+
+	/* row-security is not supported prior to v9.4 */
+	if (fout->remoteVersion < 90400)
+		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.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_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].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;
+
+	query = createPQExpBuffer();
+	delqry = createPQExpBuffer();
+	appendPQExpBuffer(query, "ALTER TABLE %s SET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	appendPQExpBuffer(delqry, "ALTER TABLE %s RESET ROW SECURITY ",
+					  fmtId(tbinfo->dobj.name));
+	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);
+	}
+	appendPQExpBuffer(query, "FOR %s TO %s;\n", cmd, rsinfo->rsecqual);
+	appendPQExpBuffer(delqry, "FOR %s;\n", 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,
@@ -4214,6 +4343,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relhastriggers;
 	int			i_relhasindex;
 	int			i_relhasrules;
+	int			i_relhasrowsec;
 	int			i_relhasoids;
 	int			i_relfrozenxid;
 	int			i_toastoid;
@@ -4252,7 +4382,45 @@ getTables(Archive *fout, int *numTables)
 	 * we cannot correctly identify inherited columns, owned sequences, etc.
 	 */
 
-	if (fout->remoteVersion >= 90300)
+	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, "
+						  "c.relhasrowsecurity, "
+						  "c.relfrozenxid, tc.oid AS toid, "
+						  "tc.relfrozenxid AS tfrozenxid, "
+						  "c.relpersistence, c.relispopulated, "
+						  "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(c.reloptions, ', ') AS reloptions, "
+						  "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 >= 90300)
 	{
 		/*
 		 * Left join to pick up dependency info linking sequences to their
@@ -4264,6 +4432,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, c.relispopulated, "
@@ -4303,6 +4472,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "c.relpersistence, 't' as relispopulated, "
@@ -4340,6 +4510,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4376,6 +4547,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 						  "c.relchecks, c.relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4412,6 +4584,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s c.relowner) AS rolname, "
 					  "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, "
 						  "c.relhasindex, c.relhasrules, c.relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "c.relfrozenxid, tc.oid AS toid, "
 						  "tc.relfrozenxid AS tfrozenxid, "
 						  "'p' AS relpersistence, 't' as relispopulated, "
@@ -4448,6 +4621,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4484,6 +4658,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4516,6 +4691,7 @@ getTables(Archive *fout, int *numTables)
 						  "(%s relowner) AS rolname, "
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4543,6 +4719,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 AS relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4580,6 +4757,7 @@ getTables(Archive *fout, int *numTables)
 						  "relchecks, (reltriggers <> 0) AS relhastriggers, "
 						  "relhasindex, relhasrules, "
 						  "'t'::bool AS relhasoids, "
+						  "'f'::bool AS relhasrowsecurity, "
 						  "0 as relfrozenxid, "
 						  "0 AS toid, "
 						  "0 AS tfrozenxid, "
@@ -4627,6 +4805,7 @@ getTables(Archive *fout, int *numTables)
 	i_relhastriggers = PQfnumber(res, "relhastriggers");
 	i_relhasindex = PQfnumber(res, "relhasindex");
 	i_relhasrules = PQfnumber(res, "relhasrules");
+	i_relhasrowsec = PQfnumber(res, "relhasrowsecurity");
 	i_relhasoids = PQfnumber(res, "relhasoids");
 	i_relfrozenxid = PQfnumber(res, "relfrozenxid");
 	i_toastoid = PQfnumber(res, "toid");
@@ -4675,6 +4854,7 @@ getTables(Archive *fout, int *numTables)
 		tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0);
 		tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0);
 		tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0);
+		tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0);
 		tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0);
 		tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0);
 		tblinfo[i].relpages = atoi(PQgetvalue(res, i, i_relpages));
@@ -7758,6 +7938,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 						 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 */
@@ -14935,6 +15118,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			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
index 2c5971c..4dc00db 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -111,7 +111,8 @@ typedef enum
 	DO_PRE_DATA_BOUNDARY,
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
-	DO_REFRESH_MATVIEW
+	DO_REFRESH_MATVIEW,
+	DO_ROW_SECURITY,
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -244,6 +245,7 @@ typedef struct _tableInfo
 	bool		hasindex;		/* does it have any indexes? */
 	bool		hasrules;		/* does it have any rules? */
 	bool		hastriggers;	/* does it have any triggers? */
+	bool		hasrowsec;		/* does it have any row-security policy? */
 	bool		hasoids;		/* does it have OIDs? */
 	uint32		frozenxid;		/* for restore frozen xid */
 	Oid			toast_oid;		/* for restore toast frozen xid */
@@ -482,6 +484,14 @@ typedef struct _blobInfo
 	char	   *blobacl;
 } BlobInfo;
 
+typedef struct _rowSecurityInfo
+{
+	DumpableObject dobj;
+	TableInfo  *rstable;
+	char	   *rseccmd;
+	char	   *rsecqual;
+} RowSecurityInfo;
+
 /* global decls */
 extern bool force_quotes;		/* double-quotes for identifiers flag */
 extern bool g_verbose;			/* verbose flag */
@@ -573,5 +583,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
 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
index 141e713..5c5777c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1342,6 +1342,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "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/psql/describe.c b/src/bin/psql/describe.c
index dad1d5a..de09410 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2697,6 +2697,10 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&buf,
 			  ",\n  pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
 						  gettext_noop("Description"));
+		if (pset.sversion >= 90300)
+			appendPQExpBuffer(&buf,
+			  ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"",
+							  gettext_noop("Row-security"));
 	}
 
 	appendPQExpBuffer(&buf,
@@ -2706,6 +2710,9 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 		appendPQExpBuffer(&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 >= 90300)
+		appendPQExpBuffer(&buf,
+		   "\n     LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid");
 
 	appendPQExpBuffer(&buf, "\nWHERE c.relkind IN (");
 	if (showTables)
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3aefbb5e..c96e8d4 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	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
index 4860e98..f45e42b 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -313,6 +313,11 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 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, 3819, on pg_rowsecurity using btree(oid oid_ops));
+#define RowSecurityOidIndexId				3819
+DECLARE_UNIQUE_INDEX(pg_rowsecurity_relid_index, 3839, on pg_rowsecurity using btree(rsecrelid oid_ops, rseccmd char_ops));
+#define RowSecurityRelidIndexId				3839
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 49c4f6f..6a16819 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -64,6 +64,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP BKI_ROWTYPE_OID(83) BKI_SCHEMA_MACRO
 	bool		relhaspkey;		/* has (or has had) PRIMARY KEY index */
 	bool		relhasrules;	/* has (or has had) any rules */
 	bool		relhastriggers; /* has (or has had) any TRIGGERs */
+	bool		relhasrowsecurity;	/* has (or has had) row-security policy */
 	bool		relhassubclass; /* has (or has had) derived classes */
 	bool		relispopulated; /* matview currently holds query results */
 	TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */
@@ -93,7 +94,7 @@ typedef FormData_pg_class *Form_pg_class;
  * ----------------
  */
 
-#define Natts_pg_class					28
+#define Natts_pg_class					29
 #define Anum_pg_class_relname			1
 #define Anum_pg_class_relnamespace		2
 #define Anum_pg_class_reltype			3
@@ -116,12 +117,13 @@ typedef FormData_pg_class *Form_pg_class;
 #define Anum_pg_class_relhaspkey		20
 #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_relfrozenxid		25
-#define Anum_pg_class_relminmxid		26
-#define Anum_pg_class_relacl			27
-#define Anum_pg_class_reloptions		28
+#define Anum_pg_class_relhasrowsecurity	23
+#define Anum_pg_class_relhassubclass	24
+#define Anum_pg_class_relispopulated	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
@@ -136,13 +138,13 @@ typedef FormData_pg_class *Form_pg_class;
  * 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 3 1 _null_ _null_ ));
+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 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 28 0 t f f f f t 3 1 _null_ _null_ ));
+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 f t 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 0000000..a068b35
--- /dev/null
+++ b/src/include/catalog/pg_rowsecurity.h
@@ -0,0 +1,76 @@
+/*
+ * 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_rowlevelsec definition. cpp turns this into
+ *		typedef struct FormData_pg_rowlevelsec
+ * ----------------
+ */
+#define RowSecurityRelationId	3838
+
+CATALOG(pg_rowsecurity,3838)
+{
+	/* Oid of the relation that has row-security policy */
+	Oid				rsecrelid;
+
+	/* One of ROWSECURITY_CMD_* below */
+	char			rseccmd;
+#ifdef CATALOG_VARLEN
+	pg_node_tree	rsecqual;
+#endif
+} FormData_pg_rowsecurity;
+
+/* ----------------
+ *		Form_pg_rowlevelsec corresponds to a pointer to a row with
+ *		the format of pg_rowlevelsec relation.
+ * ----------------
+ */
+typedef FormData_pg_rowsecurity *Form_pg_rowsecurity;
+
+/* ----------------
+ * 		compiler constants for pg_rowlevelsec
+ * ----------------
+ */
+#define Natts_pg_rowsecurity				3
+#define Anum_pg_rowsecurity_rsecrelid		1
+#define Anum_pg_rowsecurity_rseccmd			2
+#define Anum_pg_rowsecurity_rsecqual		3
+
+#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
+{
+	MemoryContext		rscxt;
+	RowSecurityEntry	rsall;	/* row-security policy for ALL */
+} RowSecurityDesc;
+
+extern void	RelationBuildRowSecurity(Relation relation);
+extern void	ATExecSetRowSecurity(Relation relation,
+								 const char *cmdname, Node *clause);
+extern void	RemoveRowSecurityById(Oid relationId);
+
+#endif  /* PG_ROWSECURITY_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index edced29..bd14531 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -279,6 +279,7 @@ extern int	trace_recovery(int trace_level);
 /* 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/execnodes.h b/src/include/nodes/execnodes.h
index 3b430e0..5f8b7f7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -308,6 +308,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		rowSecurity				for row-security checks
+ *		rowSecParams			param-list if row-security has SubLink
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -329,6 +331,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	Node	   *ri_rowSecurity;
+	List	   *ri_rowSecParams;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index fe7cfd3..23c3553 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -24,6 +24,7 @@
 #define QTW_IGNORE_RANGE_TABLE		0x08		/* skip rangetable entirely */
 #define QTW_EXAMINE_RTES			0x10		/* examine RTEs */
 #define QTW_DONT_COPY_QUERY			0x20		/* do not copy top Query */
+#define QTW_IGNORE_RETURNING		0x40		/* skip returning clause */
 
 
 extern Oid	exprType(const Node *expr);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 51fef68..72ca771 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -31,7 +31,8 @@ typedef enum QuerySource
 	QSRC_PARSER,				/* added by parse analysis (now unused) */
 	QSRC_INSTEAD_RULE,			/* added by unconditional INSTEAD rule */
 	QSRC_QUAL_INSTEAD_RULE,		/* added by conditional INSTEAD rule */
-	QSRC_NON_INSTEAD_RULE		/* added by non-INSTEAD rule */
+	QSRC_NON_INSTEAD_RULE,		/* added by non-INSTEAD rule */
+	QSRC_ROW_SECURITY,			/* added by row-security */
 } QuerySource;
 
 /* Sort ordering options for ORDER BY and CREATE INDEX */
@@ -112,7 +113,9 @@ typedef struct Query
 
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
-
+	int			sourceRelation;	/* rtable index of source relation for
+								 * UPDATE/DELETE, if not identical with
+								 * resultRelation; 0 for elsewhere */
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -733,6 +736,11 @@ typedef struct RangeTblEntry
 	 */
 	Query	   *subquery;		/* the sub-query */
 	bool		security_barrier;		/* is from security_barrier view? */
+	Oid			rowsec_relid;	/* OID of the original relation, if this
+								 * sub-query originated from row-security
+								 * policy on the relation. Elsewhere, it
+								 * should be InvalidOid.
+								 */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
@@ -1284,6 +1292,8 @@ typedef enum AlterTableType
 	AT_DropInherit,				/* NO INHERIT parent */
 	AT_AddOf,					/* OF <type_name> */
 	AT_DropOf,					/* NOT OF */
+	AT_SetRowSecurity,			/* SET ROW SECURITY (...) */
+	AT_ResetRowSecurity,		/* RESET ROW SECURITY */
 	AT_GenericOptions			/* OPTIONS (...) */
 } AlterTableType;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 44ea0b7..889dc7d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -67,6 +67,8 @@ typedef struct PlannedStmt
 	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
 
 	int			nParamExec;		/* number of PARAM_EXEC Params used */
+
+	Oid			planUserId;		/* user-id this plan assumed, or InvalidOid */
 } PlannedStmt;
 
 /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index a2853fb..e7eb8cf 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -98,6 +98,8 @@ typedef struct PlannerGlobal
 	Index		lastRowMarkId;	/* highest PlanRowMark ID assigned */
 
 	bool		transientPlan;	/* redo plan when TransactionXmin changes? */
+
+	Oid			planUserId;		/* User-Id to be assumed on this plan */
 } PlannerGlobal;
 
 /* macro for fetching the Plan associated with a SubPlan node */
@@ -1426,6 +1428,10 @@ typedef struct AppendRelInfo
 	 */
 	Index		parent_relid;	/* RT index of append parent rel */
 	Index		child_relid;	/* RT index of append child rel */
+	Index		child_result;	/* RT index of append child rel's source,
+								 * if source of result relation is not
+								 * identical. Elsewhere, 0.
+								 */
 
 	/*
 	 * For an inheritance appendrel, the parent and child are both regular
diff --git a/src/include/optimizer/rowsecurity.h b/src/include/optimizer/rowsecurity.h
new file mode 100644
index 0000000..ff4dd9a
--- /dev/null
+++ b/src/include/optimizer/rowsecurity.h
@@ -0,0 +1,27 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 bool	copy_row_security_policy(CopyStmt *stmt,
+									 Relation relation, List *attnums);
+extern void	apply_row_security_policy(PlannerInfo *root);
+
+#endif	/* ROWSECURITY_H */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index bea3b07..3910853 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -63,7 +63,8 @@ typedef enum ParseExprKind
 	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_TRIGGER_WHEN,		/* WHEN condition in CREATE TRIGGER */
+	EXPR_KIND_ROW_SECURITY,			/* ROW SECURITY policy for a table */
 } ParseExprKind;
 
 
diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h
index f0604b0..6b6c364 100644
--- a/src/include/rewrite/rewriteHandler.h
+++ b/src/include/rewrite/rewriteHandler.h
@@ -18,6 +18,7 @@
 #include "nodes/parsenodes.h"
 
 extern List *QueryRewrite(Query *parsetree);
+extern void	QueryRewriteExpr(Node *node, List *activeRIRs);
 extern void AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown);
 
 extern Node *build_column_default(Relation rel, int attrno);
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 72f8491..35819fa 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -128,6 +128,8 @@ typedef struct CachedPlan
 	bool		is_oneshot;		/* is it a "oneshot" plan? */
 	bool		is_saved;		/* is CachedPlan in a long-lived context? */
 	bool		is_valid;		/* is the stmt_list currently valid? */
+	Oid			planUserId;		/* is user-id that is assumed on this cached
+								   plan, or InvalidOid if portable for anybody */
 	TransactionId saved_xmin;	/* if valid, replan when TransactionXmin
 								 * changes from this value */
 	int			generation;		/* parent's generation number for this plan */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 589c9a8..4e4ac65 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -18,6 +18,7 @@
 #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"
@@ -109,6 +110,7 @@ typedef struct RelationData
 	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 */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
new file mode 100644
index 0000000..04591df
--- /dev/null
+++ b/src/test/regress/expected/rowsecurity.out
@@ -0,0 +1,950 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+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);
+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
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   4 |      1 | rls_regress_user1 | my first manga        | manga
+(4 rows)
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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 |   2 |      2 | rls_regress_user1 | my second novel       | novel
+  11 |   1 |      1 | rls_regress_user1 | my first novel        | novel
+  22 |   6 |      1 | rls_regress_user2 | great science fiction | science fiction
+  22 |   3 |      2 | rls_regress_user1 | my science fiction    | science fiction
+  33 |   7 |      2 | rls_regress_user2 | great technology book | technology
+  44 |   8 |      1 | rls_regress_user2 | great manga           | manga
+  44 |   5 |      2 | rls_regress_user1 | my second manga       | manga
+  44 |   4 |      1 | rls_regress_user1 | my first 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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ERROR:  must be owner of relation document
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+ERROR:  must be owner of relation document
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+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);
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+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'); -- failed
+ERROR:  insert or update on table "document" violates foreign key constraint "document_cid_fkey"
+DETAIL:  Key (cid)=(33) is not present in table "category".
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+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;
+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);
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (a % 2 = 1); -- be odd number
+SELECT * FROM t1;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+SELECT * FROM t1 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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b);
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         Filter: f_leak(t1.b)
+         ->  Seq Scan on t1 t1_1
+               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)
+
+-- reference to system column
+SELECT oid, * FROM t1;
+ oid | a |  b  
+-----+---+-----
+ 102 | 2 | bbb
+ 104 | 4 | ddd
+ 201 | 1 | abc
+ 203 | 3 | cde
+ 301 | 1 | xxx
+ 302 | 2 | yyy
+ 303 | 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- reference to whole-row reference
+SELECT *,t1 FROM t1;
+ a |  b  |   t1    
+---+-----+---------
+ 2 | bbb | (2,bbb)
+ 4 | ddd | (4,ddd)
+ 1 | abc | (1,abc)
+ 3 | cde | (3,cde)
+ 1 | xxx | (1,xxx)
+ 2 | yyy | (2,yyy)
+ 3 | zzz | (3,zzz)
+(7 rows)
+
+EXPLAIN (costs off) SELECT *,t1 FROM t1;
+             QUERY PLAN              
+-------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a % 2) = 0)
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a % 2) = 1)
+   ->  Seq Scan on t3
+(8 rows)
+
+-- for share/update lock
+SELECT * FROM t1 FOR SHARE;
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               ->  Seq Scan on t1 t1_1
+                     Filter: ((a % 2) = 0)
+         ->  Subquery Scan on t2
+               ->  Seq Scan on t2 t2_1
+                     Filter: ((a % 2) = 1)
+         ->  Seq Scan on t3
+(9 rows)
+
+SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+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
+ a |  b  
+---+-----
+ 2 | bbb
+ 4 | ddd
+ 1 | abc
+ 3 | cde
+ 1 | xxx
+ 2 | yyy
+ 3 | zzz
+(7 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE f_leak(b) FOR SHARE;
+                QUERY PLAN                 
+-------------------------------------------
+ LockRows
+   ->  Append
+         ->  Subquery Scan on t1
+               Filter: f_leak(t1.b)
+               ->  Seq Scan on t1 t1_1
+                     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)
+(12 rows)
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+2	bbb
+4	ddd
+COPY t1 TO stdout WITH OIDS;
+102	2	bbb
+104	4	ddd
+COPY t2(c,b) TO stdout WITH OIDS;
+201	1.1	abc
+203	3.3	cde
+COPY (SELECT * FROM t1) TO stdout;
+2	bbb
+4	ddd
+1	abc
+3	cde
+1	xxx
+2	yyy
+3	zzz
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+ERROR:  table "document" does not have OIDs
+--
+-- 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%';
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+ERROR:  infinite recursion detected for relation "s1"
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (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)
+                     ->  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)
+(11 rows)
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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
+                     ->  Subquery Scan on s2
+                           Filter: (s2.y ~~ '%af%'::text)
+                           ->  Seq Scan on s2 s2_1
+                                 Filter: ((x % 2) = 0)
+(11 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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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 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
+ 1 | abc
+ 1 | xxx
+ 2 | yyy
+(4 rows)
+
+EXPLAIN (costs off) EXECUTE p1(2);
+                     QUERY PLAN                     
+----------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a <= 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a <= 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a <= 2)
+(9 rows)
+
+-- superuser is allowed to bypass RLS checks
+RESET SESSION AUTHORIZATION;
+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;
+EXECUTE p2(2);
+ a |  b  
+---+-----
+ 2 | bbb
+ 2 | yyy
+(2 rows)
+
+EXPLAIN (costs off) EXECUTE p2(2);
+                    QUERY PLAN                     
+---------------------------------------------------
+ Append
+   ->  Subquery Scan on t1
+         ->  Seq Scan on t1 t1_1
+               Filter: ((a = 2) AND ((a % 2) = 0))
+   ->  Subquery Scan on t2
+         ->  Seq Scan on t2 t2_1
+               Filter: ((a = 2) AND ((a % 2) = 1))
+   ->  Seq Scan on t3
+         Filter: (a = 2)
+(9 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
+   ->  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)
+
+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
+   ->  Subquery Scan on t1_1
+         Filter: f_leak(t1_1.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
+--
+ALTER TABLE category RESET ROW SECURITY 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 | document | table | rls_regress_user0 | 16 kB      |             | (dauthor = "current_user"())
+ 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 |             | 
+(8 rows)
+
+--
+-- Clean up objects
+--
+RESET SESSION AUTHORIZATION;
+DROP SCHEMA rls_regress_schema CASCADE;
+NOTICE:  drop cascades to 10 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 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/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 432d39a..3b9123d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -120,6 +120,7 @@ SELECT relname, relhasindex
  pg_proc                 | t
  pg_range                | t
  pg_rewrite              | t
+ pg_rowsecurity          | t
  pg_seclabel             | t
  pg_shdepend             | t
  pg_shdescription        | t
@@ -166,7 +167,7 @@ SELECT relname, relhasindex
  timetz_tbl              | f
  tinterval_tbl           | f
  varchar_tbl             | f
-(155 rows)
+(156 rows)
 
 --
 -- another sanity check: every system catalog that has OIDs should have
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fd08e8d..367df00 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -83,7 +83,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi
 # ----------
 # Another group of parallel tests
 # ----------
-test: privileges security_label collate matview lock
+test: privileges rowsecurity security_label collate matview lock
 
 # ----------
 # Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 1ed059b..5883a64 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -93,6 +93,7 @@ test: delete
 test: namespace
 test: prepared_xacts
 test: privileges
+test: rowsecurity
 test: security_label
 test: collate
 test: matview
diff --git a/src/test/regress/sql/rowsecurity.sql b/src/test/regress/sql/rowsecurity.sql
new file mode 100644
index 0000000..55d4aad
--- /dev/null
+++ b/src/test/regress/sql/rowsecurity.sql
@@ -0,0 +1,298 @@
+--
+-- 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 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 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
+);
+INSERT INTO uaccount VALUES
+    ('rls_regress_user0', 99),
+    ('rls_regress_user1',  1),
+    ('rls_regress_user2',  2),
+    ('rls_regress_user3',  3);
+GRANT SELECT ON uaccount TO public;
+
+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 higher than or equal to document's one
+ALTER TABLE document SET ROW SECURITY FOR ALL
+    TO (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);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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 TABLE document SET ROW SECURITY FOR ALL TO (true);     -- fail
+ALTER TABLE document RESET ROW SECURITY FOR ALL;             -- fail
+
+SET SESSION AUTHORIZATION rls_regress_user0;
+ALTER TABLE document SET ROW SECURITY FOR ALL TO (dauthor = current_user);
+
+-- viewpoint from rls_regress_user1 again
+SET SESSION AUTHORIZATION rls_regress_user1;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+-- viewpoint from rls_regress_user2 again
+SET SESSION AUTHORIZATION rls_regress_user2;
+SELECT * FROM document WHERE f_leak(dtitle);
+SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle);
+
+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;
+ALTER TABLE category SET ROW SECURITY FOR ALL
+    TO (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;    -- failed
+
+-- 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'); -- failed
+
+-- database superuser can bypass RLS policy
+RESET SESSION AUTHORIZATION;
+SELECT * FROM document;
+SELECT * FROM category;
+
+--
+-- Table inheritance and RLS policy
+--
+SET SESSION AUTHORIZATION rls_regress_user0;
+
+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);
+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
+\.
+
+ALTER TABLE t1 SET ROW SECURITY FOR ALL TO (a % 2 = 0); -- be even number
+ALTER TABLE t2 SET ROW SECURITY FOR ALL TO (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 * 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;
+
+--
+-- COPY TO statement 
+--
+COPY t1 TO stdout;
+COPY t1 TO stdout WITH OIDS;
+COPY t2(c,b) TO stdout WITH OIDS;
+COPY (SELECT * FROM t1) TO stdout;
+COPY document TO stdout WITH OIDS;	-- failed (no oid column)
+
+--
+-- 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%';
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (a in (select x from s2 where y like '%2f%'));
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL
+   TO (x in (select a from s1 where b like '%22%'));
+
+SELECT * FROM s1 WHERE f_leak(b);	-- fail (infinite recursion)
+
+ALTER TABLE s2 SET ROW SECURITY FOR ALL TO (x % 2 = 0);
+
+SELECT * FROM s1 WHERE f_leak(b);	-- OK
+EXPLAIN SELECT * FROM only s1 WHERE f_leak(b);
+
+ALTER TABLE s1 SET ROW SECURITY FOR ALL
+   TO (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 TABLE s2 SET ROW SECURITY FOR ALL
+   TO (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;
+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;
+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
+--
+ALTER TABLE category RESET ROW SECURITY 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;


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 11:56                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
@ 2013-09-07 01:23                       ` Peter Eisentraut <peter_e@gmx.net>
  2013-09-22 06:33                         ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Peter Eisentraut @ 2013-09-07 01:23 UTC (permalink / raw)
  To: Kohei KaiGai <kaigai@kaigai.gr.jp>; +Cc: Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

On Wed, 2013-08-28 at 13:56 +0200, Kohei KaiGai wrote:
> The attached patch fixed the portion I was pointed out, so its overall
> design has not been changed so much.

The documentation doesn't build:

openjade:catalogs.sgml:237:28:X: reference to non-existent ID "CATALOG-PG-ROWLEVELSEC"




-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
  2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
  2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-08-28 11:56                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-09-07 01:23                       ` Re: [v9.4] row level security Peter Eisentraut <peter_e@gmx.net>
@ 2013-09-22 06:33                         ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-09-22 06:33 UTC (permalink / raw)
  To: Peter Eisentraut <peter_e@gmx.net>; +Cc: Greg Smith <greg@2ndquadrant.com>; pgsql-hackers

Now I'm trying to tackle the covert-channel problem that Korotkov
pointed out at upthread.

The attached patch works "almost" well. It prevents to print number of
rows being filtered
if the target plan node is under sub-query with security-barrier
attribute; because row-
level security feature is constructed on existing security-barrier
view infrastructure.

One remaining issue is that planner pulls-up underlying relation scan
if top-level
sub-query is enough simple; probably, in case when targetlist of upper sub-query
is compatible with underlying relation scan.

See, the example below:
postgres=# CREATE TABLE t1 (a int primary key, b int);
CREATE TABLE
postgres=# INSERT INTO t1 VALUES (1,1111),(2,2222),(3,3333),(4,4444),(5,5555);
INSERT 0 5
postgres=# CREATE VIEW v1 WITH(security_barrier) AS SELECT * FROM t1
WHERE a % 2 = 1;
CREATE VIEW
postgres=# SET enable_seqscan = off;
SET
postgres=# EXPLAIN ANALYZE SELECT 1 FROM v1 WHERE b BETWEEN 2000 AND 4000;
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Subquery Scan on v1  (cost=10000000000.00..10000000052.81 rows=1
width=0) (actual time=0.016..0.017 rows=1 loops=1)
   ->  Seq Scan on t1  (cost=10000000000.00..10000000052.80 rows=1
width=8) (actual time=0.014..0.015 rows=1 loops=1)
         Filter: ((b >= 2000) AND (b <= 4000) AND ((a % 2) = 1))
 Total runtime: 0.067 ms
(4 rows)

According to the scenario  that Korotkov pointed out, number of
filtered rows shall be
printed, thus, it allows to estimate particular value with log(N) times trials.
However, this example hides number of rows being done within
security-barrier sub-
query as I expected.

On the other hand, if planner pulled-up underlying relation scan, it
does NOT work fine.

postgres=# EXPLAIN ANALYZE SELECT * FROM v1 WHERE b BETWEEN 2000 AND 4000;
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Seq Scan on t1  (cost=10000000000.00..10000000052.80 rows=1 width=8)
(actual time=0.019..0.021 rows=1 loops=1)
   Filter: ((b >= 2000) AND (b <= 4000) AND ((a % 2) = 1))
   Rows Removed by Filter: 4
 Total runtime: 0.055 ms
(4 rows)

It seems to me we need to prohibit to pull-up security-barrier
sub-query here, or
to mark underlying relation scan security-barrier attribute to solve this issue.
(I'm still looking for which routine handles this pull-up job,
however, I didn't find it yet.)

How about opinion for this solution?

Thanks,

2013/9/7 Peter Eisentraut <peter_e@gmx.net>:
> On Wed, 2013-08-28 at 13:56 +0200, Kohei KaiGai wrote:
>> The attached patch fixed the portion I was pointed out, so its overall
>> design has not been changed so much.
>
> The documentation doesn't build:
>
> openjade:catalogs.sgml:237:28:X: reference to non-existent ID "CATALOG-PG-ROWLEVELSEC"
>
>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

  [application/octet-stream] explain_hide_filtered.v0.patch (13.0K, ../../CADyhKSWVVZdSSwdbP6+ReHEjr8RNFV7pCj_SgB9kGqepNfdjVw@mail.gmail.com/2-explain_hide_filtered.v0.patch)
  download | inline diff:
 src/backend/commands/explain.c | 107 +++++++++++++++++++++++++++--------------
 1 file changed, 70 insertions(+), 37 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 91bea51..4ea4920 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -57,7 +57,7 @@ static void ExplainPreScanMemberNodes(List *plans, PlanState **planstates,
 static void ExplainPreScanSubPlans(List *plans, Bitmapset **rels_used);
 static void ExplainNode(PlanState *planstate, List *ancestors,
 			const char *relationship, const char *plan_name,
-			ExplainState *es);
+			bool security_barrier, ExplainState *es);
 static void show_plan_tlist(PlanState *planstate, List *ancestors,
 				ExplainState *es);
 static void show_expression(Node *node, const char *qlabel,
@@ -92,9 +92,12 @@ static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
 static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
 static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
 static void ExplainMemberNodes(List *plans, PlanState **planstates,
-				   List *ancestors, ExplainState *es);
+				List *ancestors, bool security_barrier, ExplainState *es);
 static void ExplainSubPlans(List *plans, List *ancestors,
-				const char *relationship, ExplainState *es);
+				const char *relationship, bool security_barrier,
+				ExplainState *es);
+static void ExplainSubQuery(SubqueryScanState *qstate, List *ancestors,
+				bool security_barrier, ExplainState *es);
 static void ExplainProperty(const char *qlabel, const char *value,
 				bool numeric, ExplainState *es);
 static void ExplainOpenGroup(const char *objtype, const char *labelname,
@@ -555,7 +558,7 @@ ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
 	es->rtable = queryDesc->plannedstmt->rtable;
 	ExplainPreScanNode(queryDesc->planstate, &rels_used);
 	es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
-	ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
+	ExplainNode(queryDesc->planstate, NIL, NULL, NULL, false, es);
 }
 
 /*
@@ -794,6 +797,10 @@ ExplainPreScanSubPlans(List *plans, Bitmapset **rels_used)
  * (eg, "Outer", "Inner"); it can be null at top level.  plan_name is an
  * optional name to be attached to the node.
  *
+ * security_barrier shows whether this plan come from a view with security
+ * barrier attribute, or not. If true, it restrain a part of output to
+ * avoid information leakage to be invisible.
+ *
  * In text format, es->indent is controlled in this function since we only
  * want it to change at plan-node boundaries.  In non-text formats, es->indent
  * corresponds to the nesting depth of logical output groups, and therefore
@@ -802,7 +809,7 @@ ExplainPreScanSubPlans(List *plans, Bitmapset **rels_used)
 static void
 ExplainNode(PlanState *planstate, List *ancestors,
 			const char *relationship, const char *plan_name,
-			ExplainState *es)
+			bool security_barrier, ExplainState *es)
 {
 	Plan	   *plan = planstate->plan;
 	const char *pname;			/* node type name for text output */
@@ -1210,26 +1217,26 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_IndexScan:
 			show_scan_qual(((IndexScan *) plan)->indexqualorig,
 						   "Index Cond", planstate, ancestors, es);
-			if (((IndexScan *) plan)->indexqualorig)
+			if (((IndexScan *) plan)->indexqualorig && !security_barrier)
 				show_instrumentation_count("Rows Removed by Index Recheck", 2,
 										   planstate, es);
 			show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
 						   "Order By", planstate, ancestors, es);
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
 		case T_IndexOnlyScan:
 			show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
 						   "Index Cond", planstate, ancestors, es);
-			if (((IndexOnlyScan *) plan)->indexqual)
+			if (((IndexOnlyScan *) plan)->indexqual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Index Recheck", 2,
 										   planstate, es);
 			show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
 						   "Order By", planstate, ancestors, es);
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			if (es->analyze)
@@ -1243,7 +1250,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_BitmapHeapScan:
 			show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
 						   "Recheck Cond", planstate, ancestors, es);
-			if (((BitmapHeapScan *) plan)->bitmapqualorig)
+			if (((BitmapHeapScan *) plan)->bitmapqualorig && !security_barrier)
 				show_instrumentation_count("Rows Removed by Index Recheck", 2,
 										   planstate, es);
 			/* FALL THRU */
@@ -1253,7 +1260,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_WorkTableScan:
 		case T_SubqueryScan:
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
@@ -1263,7 +1270,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 								"Function Call", planstate, ancestors,
 								es->verbose, es);
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
@@ -1279,14 +1286,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					tidquals = list_make1(make_orclause(tidquals));
 				show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
 				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-				if (plan->qual)
+				if (plan->qual && !security_barrier)
 					show_instrumentation_count("Rows Removed by Filter", 1,
 											   planstate, es);
 			}
 			break;
 		case T_ForeignScan:
 			show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
@@ -1294,11 +1301,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_NestLoop:
 			show_upper_qual(((NestLoop *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
-			if (((NestLoop *) plan)->join.joinqual)
+			if (((NestLoop *) plan)->join.joinqual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 2,
 										   planstate, es);
 			break;
@@ -1307,11 +1314,11 @@ ExplainNode(PlanState *planstate, List *ancestors,
 							"Merge Cond", planstate, ancestors, es);
 			show_upper_qual(((MergeJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
-			if (((MergeJoin *) plan)->join.joinqual)
+			if (((MergeJoin *) plan)->join.joinqual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 2,
 										   planstate, es);
 			break;
@@ -1320,18 +1327,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 							"Hash Cond", planstate, ancestors, es);
 			show_upper_qual(((HashJoin *) plan)->join.joinqual,
 							"Join Filter", planstate, ancestors, es);
-			if (((HashJoin *) plan)->join.joinqual)
+			if (((HashJoin *) plan)->join.joinqual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Join Filter", 1,
 										   planstate, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 2,
 										   planstate, es);
 			break;
 		case T_Agg:
 		case T_Group:
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
@@ -1347,7 +1354,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			show_upper_qual((List *) ((Result *) plan)->resconstantqual,
 							"One-Time Filter", planstate, ancestors, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
-			if (plan->qual)
+			if (plan->qual && !security_barrier)
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
@@ -1487,17 +1494,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 
 	/* initPlan-s */
 	if (planstate->initPlan)
-		ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
+		ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan",
+						security_barrier, es);
 
 	/* lefttree */
 	if (outerPlanState(planstate))
 		ExplainNode(outerPlanState(planstate), ancestors,
-					"Outer", NULL, es);
+					"Outer", NULL, security_barrier, es);
 
 	/* righttree */
 	if (innerPlanState(planstate))
 		ExplainNode(innerPlanState(planstate), ancestors,
-					"Inner", NULL, es);
+					"Inner", NULL, security_barrier, es);
 
 	/* special child plans */
 	switch (nodeTag(plan))
@@ -1505,31 +1513,31 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ModifyTable:
 			ExplainMemberNodes(((ModifyTable *) plan)->plans,
 							   ((ModifyTableState *) planstate)->mt_plans,
-							   ancestors, es);
+							   ancestors, security_barrier, es);
 			break;
 		case T_Append:
 			ExplainMemberNodes(((Append *) plan)->appendplans,
 							   ((AppendState *) planstate)->appendplans,
-							   ancestors, es);
+							   ancestors, security_barrier, es);
 			break;
 		case T_MergeAppend:
 			ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
 							   ((MergeAppendState *) planstate)->mergeplans,
-							   ancestors, es);
+							   ancestors, security_barrier, es);
 			break;
 		case T_BitmapAnd:
 			ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
 							   ((BitmapAndState *) planstate)->bitmapplans,
-							   ancestors, es);
+							   ancestors, security_barrier, es);
 			break;
 		case T_BitmapOr:
 			ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
 							   ((BitmapOrState *) planstate)->bitmapplans,
-							   ancestors, es);
+							   ancestors, security_barrier, es);
 			break;
 		case T_SubqueryScan:
-			ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
-						"Subquery", NULL, es);
+			ExplainSubQuery((SubqueryScanState *) planstate,
+							ancestors, security_barrier, es);
 			break;
 		default:
 			break;
@@ -1537,7 +1545,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 
 	/* subPlan-s */
 	if (planstate->subPlan)
-		ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
+		ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan",
+						security_barrier, es);
 
 	/* end of child plans */
 	if (haschildren)
@@ -2090,14 +2099,14 @@ show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
  */
 static void
 ExplainMemberNodes(List *plans, PlanState **planstates,
-				   List *ancestors, ExplainState *es)
+				   List *ancestors, bool security_barrier, ExplainState *es)
 {
 	int			nplans = list_length(plans);
 	int			j;
 
 	for (j = 0; j < nplans; j++)
 		ExplainNode(planstates[j], ancestors,
-					"Member", NULL, es);
+					"Member", NULL, security_barrier, es);
 }
 
 /*
@@ -2108,7 +2117,8 @@ ExplainMemberNodes(List *plans, PlanState **planstates,
  */
 static void
 ExplainSubPlans(List *plans, List *ancestors,
-				const char *relationship, ExplainState *es)
+				const char *relationship, bool security_barrier,
+				ExplainState *es)
 {
 	ListCell   *lst;
 
@@ -2118,11 +2128,34 @@ ExplainSubPlans(List *plans, List *ancestors,
 		SubPlan    *sp = (SubPlan *) sps->xprstate.expr;
 
 		ExplainNode(sps->planstate, ancestors,
-					relationship, sp->plan_name, es);
+					relationship, sp->plan_name, security_barrier, es);
 	}
 }
 
 /*
+ * Explain a child plan node of a particular sub-query
+ */
+static void
+ExplainSubQuery(SubqueryScanState *qstate, List *ancestors,
+				bool security_barrier, ExplainState *es)
+{
+	bool	security_barrier_child;
+
+	if (!security_barrier)
+	{
+		SubqueryScan   *qscan = (SubqueryScan *)qstate->ss.ps.plan;
+		RangeTblEntry  *rte = rt_fetch(qscan->scan.scanrelid, es->rtable);
+
+		security_barrier_child = rte->security_barrier;
+	}
+	else
+		security_barrier_child = true;
+
+	ExplainNode(qstate->subplan, ancestors, "Subquery", NULL,
+				security_barrier_child, es);
+}
+
+/*
  * Explain a property, such as sort keys or targets, that takes the form of
  * a list of unlabeled items.  "data" is a list of C strings.
  */


^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
  2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
  2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
  2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
@ 2013-07-20 13:47             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  1 sibling, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-07-20 13:47 UTC (permalink / raw)
  To: Stephen Frost <sfrost@snowman.net>; +Cc: Greg Smith <greg@2ndquadrant.com>; Karol Trzcionka <karlikt@gmail.com>; Mike Blackwell <mike.blackwell@rrd.com>; pgsql-hackers

2013/7/19 Stephen Frost <sfrost@snowman.net>:
> * Greg Smith (greg@2ndQuadrant.com) wrote:
>> On 7/18/13 7:57 PM, Karol Trzcionka wrote:
>> >Current head 4cbe3ac3e86790d05c569de4585e5075a62a9b41 -> patch applies
>> >correct (only change needed in parallel_schedule).
>> >However it fails on own regression tests (other tests pass).
>>
>> I got a rejected hunk in src/backend/nodes/nodeFuncs.c as well as
>> that parallel_schedule issue.  Maybe you didn't get the nodeFuncs
>> change but didn't notice that?  That might explain why the tests
>> didn't work for you either.
>
> The nodeFuncs.c hunk seems likely to have been impacted by the patch I
> committed today (WITH CHECK OPTION), so I doubt that was the issue.
>
>> Attached is an updated patch where I tried to only fix the two small
>> hunks of bit rot.  I get "All 140 tests passed" here, on a Mac no
>> less.
>
> Thanks for updating the patch, I ran into the failed hunks too and
> expected to have to deal with them. :)
>
Thanks for pointing out this problem. I synchronized my local master
with the upstream one, then adjusted the row-security branch.

I'll submit the patch soon, with fixing-up the portion that Tom pointed
out.

Thanks,


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
@ 2013-10-10 18:56 Marc Munro <marc@bloodnok.com>
  2013-10-16 15:24 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 1 reply; 165+ messages in thread

From: Marc Munro @ 2013-10-10 18:56 UTC (permalink / raw)
  To: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; KoheiKaiGai <kaigai@kaigai.gr.jp>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; GregSmith <greg@2ndquadrant.com>; +Cc: pgsql-hackers

On Wed, 2013-09-04 at 14:35 +0000, Robert Haas wrote:
> 
> On Fri, Aug 30, 2013 at 3:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
> > I think it's entirely sensible to question whether we should reject
> (not
> > "hold up") RLS if it has major covert-channel problems.
> 
> We've already had this argument before, about the security_barrier
[ . . . ]

Sorry for following up on this so late, I have just been trying to catch
up with the mailing lists.

I am the developer of Veil, which this thread mentioned a number of
times.  I wanted to state/confirm a number of things:

Veil is not up to date wrt Postgres versions.  I didn't release a new
version for 9.2, and when no-one complained I figured no-one other than
me was using it.  I'll happily update it if anyone wants it.

Veil makes no attempt to avoid covert channels.  It can't.

Veil is a low-level toolset designed for optimising queries about
privileges.  It allows you to build RLS with reasonable performance, but
it is not in itself a solution for RLS.

I wish the Postgres RLS project well and look forward to its release in
Postgres 9.4.  

__
Marc

Attachments:

  [application/pgp-signature] signature.asc (197B, ../../1381431409.15121.58.camel@bloodnok.com/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 165+ messages in thread

* Re: [v9.4] row level security
  2013-10-10 18:56 Re: [v9.4] row level security Marc Munro <marc@bloodnok.com>
@ 2013-10-16 15:24 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
  0 siblings, 0 replies; 165+ messages in thread

From: Kohei KaiGai @ 2013-10-16 15:24 UTC (permalink / raw)
  To: Marc Munro <marc@bloodnok.com>; +Cc: Robert Haas <robertmhaas@gmail.com>; Tom Lane <tgl@sss.pgh.pa.us>; Stephen Frost <sfrost@snowman.net>; Josh Berkus <josh@agliodbs.com>; ktm@rice.edu <ktm@rice.edu>; Alexander Korotkov <aekorotkov@gmail.com>; Oleg Bartunov <obartunov@gmail.com>; GregSmith <greg@2ndquadrant.com>; pgsql-hackers

Because of CF-2nd end, it was moved to the next commit-fest.

In my personal opinion, it probably needs a few groundwork to get RLS
commitable,
prior to RLS itself.

One is a correct handling towards the scenario that Korotkov pointed
out. (How was
it concluded?) I think it is a problem we can fix with reasonable way.
Likely, solution
is to prohibit to show number of rows being filtered if plan node is
underlying a sub-
query with security-barrier.

One other stuff I'm concerned about is, existing implementation
assumes a certain
rtable entry performs as a source relation, also result relation in same time on
DELETE and UPDATE.
We usually scan a regular relation to fetch ctid of the tuples to be
modified, then
ModifyTable deletes or updates the tuple being identified. Here has
been no matter
for long period, even if same rtable entry is used to point out a
relation to be scanned
and to be modified.
It however makes RLS implementation complicated, because this feature tries to
replace a rtable entry to relation with row-level security policy by a
simple sub-query
with security-barrier attribute. Our implementation does not assume a sub-query
performs as a result relation, so I had to have some ad-hoc coding,
like adjustment
on varno/varattno of Var objects, to avoid problem.
I think, solution is to separate a rtable entry of result-relation
from rtable entry to
be scanned. It makes easier to implement RLS feature because all we need to do
is just replacement of rtable entry for scanning stage, and no need to
care about
ModifyTable operations towards sub-query.

Is it a right direction to go?

Thanks,

2013/10/10 Marc Munro <marc@bloodnok.com>:
> On Wed, 2013-09-04 at 14:35 +0000, Robert Haas wrote:
>>
>> On Fri, Aug 30, 2013 at 3:43 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> > I think it's entirely sensible to question whether we should reject
>> (not
>> > "hold up") RLS if it has major covert-channel problems.
>>
>> We've already had this argument before, about the security_barrier
> [ . . . ]
>
> Sorry for following up on this so late, I have just been trying to catch
> up with the mailing lists.
>
> I am the developer of Veil, which this thread mentioned a number of
> times.  I wanted to state/confirm a number of things:
>
> Veil is not up to date wrt Postgres versions.  I didn't release a new
> version for 9.2, and when no-one complained I figured no-one other than
> me was using it.  I'll happily update it if anyone wants it.
>
> Veil makes no attempt to avoid covert channels.  It can't.
>
> Veil is a low-level toolset designed for optimising queries about
> privileges.  It allows you to build RLS with reasonable performance, but
> it is not in itself a solution for RLS.
>
> I wish the Postgres RLS project well and look forward to its release in
> Postgres 9.4.
>
> __
> Marc
>
>



-- 
KaiGai Kohei <kaigai@kaigai.gr.jp>


-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers



^ permalink  raw  reply  [nested|flat] 165+ messages in thread


end of thread, other threads:[~2014-01-24 03:15 UTC | newest]

Thread overview: 165+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2009-12-12 20:30 Row-Level Security Stephen Frost <sfrost@snowman.net>
2009-12-12 23:30 ` KaiGai Kohei <kaigai@kaigai.gr.jp>
2009-12-13 00:11   ` Stephen Frost <sfrost@snowman.net>
2009-12-13 00:41 ` Josh Berkus <josh@agliodbs.com>
2009-12-13 03:18   ` Robert Haas <robertmhaas@gmail.com>
2009-12-13 08:50     ` KaiGai Kohei <kaigai@kaigai.gr.jp>
2009-12-13 12:11       ` Robert Haas <robertmhaas@gmail.com>
2009-12-14 04:57         ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2009-12-14 05:35           ` Robert Haas <robertmhaas@gmail.com>
2009-12-14 06:43             ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2009-12-14 08:58               ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2009-12-14 15:04                 ` Tom Lane <tgl@sss.pgh.pa.us>
2009-12-14 11:18               ` Robert Haas <robertmhaas@gmail.com>
2009-12-14 11:52                 ` KaiGai Kohei <kaigai@kaigai.gr.jp>
2009-12-14 13:47                   ` Stephen Frost <sfrost@snowman.net>
2009-12-15 01:40                     ` KaiGai Kohei <kaigai@ak.jp.nec.com>
2009-12-15 02:06                       ` Stephen Frost <sfrost@snowman.net>
2009-12-15 03:32                       ` Robert Haas <robertmhaas@gmail.com>
2009-12-15 12:20                         ` Stephen Frost <sfrost@snowman.net>
2012-06-14 15:43 [v9.3] Row-Level Security Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-26 16:31 ` Robert Haas <robertmhaas@gmail.com>
2012-06-26 20:36   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-26 20:59     ` Tom Lane <tgl@sss.pgh.pa.us>
2012-06-27 05:18       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-27 11:21         ` Florian Pflug <fgp@phlo.org>
2012-06-27 12:23           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-27 12:42           ` Robert Haas <robertmhaas@gmail.com>
2012-06-27 13:07             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-27 15:58               ` Florian Pflug <fgp@phlo.org>
2012-06-28 15:02                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-06-28 15:29                   ` Tom Lane <tgl@sss.pgh.pa.us>
2012-06-28 15:34                     ` Florian Pflug <fgp@phlo.org>
2012-06-28 15:46                       ` Tom Lane <tgl@sss.pgh.pa.us>
2012-07-01 14:53                     ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-07-15 09:52                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-07-17 03:49                         ` Robert Haas <robertmhaas@gmail.com>
2012-07-17 04:02                           ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-09-02 21:39                             ` Dean Rasheed <dean.a.rasheed@gmail.com>
2012-09-03 20:32                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-09-05 20:49                                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-10-08 14:57                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-10-08 18:33                                     ` Dean Rasheed <dean.a.rasheed@gmail.com>
2012-10-08 19:49                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-10-15 20:19                                         ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-10-18 18:19                                           ` Alvaro Herrera <alvherre@2ndquadrant.com>
2012-10-19 09:00                                             ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-10-19 10:02                                               ` Erik Rijkers <er@xs4all.nl>
2012-10-22 13:33                                             ` Robert Haas <robertmhaas@gmail.com>
2012-10-22 16:17                                               ` Tom Lane <tgl@sss.pgh.pa.us>
2012-10-22 17:44                                                 ` Robert Haas <robertmhaas@gmail.com>
2012-10-22 20:54                                               ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-11-15 21:07                                                 ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-11-25 14:20                                                   ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-12-03 15:25                                                     ` David Fetter <david@fetter.org>
2012-12-03 15:36                                                       ` Kohei KaiGai <kaigai@kaigai.gr.jp>
2012-12-03 15:41                                                         ` Simon Riggs <simon@2ndQuadrant.com>
2012-12-03 16:09                                                           ` David Fetter <david@fetter.org>
2012-12-03 16:59                                                             ` Alvaro Herrera <alvherre@2ndquadrant.com>
2012-12-03 17:02                                                   ` Simon Riggs <simon@2ndQuadrant.com>
2013-06-13 19:44 [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-07-08 07:57 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-07-08 10:19   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-07-08 18:58     ` Re: [v9.4] row level security Mike Blackwell <mike.blackwell@rrd.com>
2013-07-18 23:57       ` Re: [v9.4] row level security Karol Trzcionka <karlikt@gmail.com>
2013-07-19 02:47         ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-07-19 03:03           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-07-19 03:09             ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-07-19 10:41               ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
2013-07-19 15:00                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-07-20 14:08                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-07-22 20:27                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-07-23 04:23                     ` Re: [v9.4] row level security Atri Sharma <atri.jiit@gmail.com>
2013-07-23 16:10                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-07-23 17:16                       ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-07-23 18:30                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-07-23 22:34                           ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-07-23 22:48                             ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-08-05 12:35                           ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
2013-08-27 21:37                   ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-08-28 06:45                     ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
2013-08-28 12:17                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-29 11:05                         ` Re: [v9.4] row level security Oleg Bartunov <obartunov@gmail.com>
2013-08-29 12:09                         ` Re: [v9.4] row level security Alexander Korotkov <aekorotkov@gmail.com>
2013-08-29 14:05                           ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-29 17:27                             ` Re: [v9.4] row level security David Fetter <david@fetter.org>
2013-08-30 10:13                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-29 14:14                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-29 14:23                             ` Re: [v9.4] row level security ktm@rice.edu <ktm@rice.edu>
2013-08-29 14:35                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-29 16:59                                 ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-08-29 17:18                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-29 17:22                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-08-29 17:28                                     ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-30 10:20                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-30 10:05                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-30 17:36                                     ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-08-30 19:27                                       ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-08-30 19:43                                         ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-30 19:57                                           ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-08-30 20:28                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-31 05:41                                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-01 13:38                                             ` Re: [v9.4] row level security Heikki Linnakangas <hlinnakangas@vmware.com>
2013-09-01 14:06                                               ` Re: [v9.4] row level security Andres Freund <andres@2ndquadrant.com>
2013-09-01 17:46                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-08-30 20:24                                           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-09-03 00:17                                             ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
2013-09-04 14:35                                           ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-09-04 14:45                                             ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-09-04 14:48                                               ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-09-04 14:50                                                 ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-09-04 15:03                                                   ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-09-04 15:24                                                     ` Re: [v9.4] row level security Kevin Grittner <kgrittn@ymail.com>
2013-09-04 15:55                                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-04 15:22                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-09-04 15:46                                                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-04 16:50                                                     ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-11-04 08:08                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-04 14:37                                                       ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-11-04 15:17                                                         ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-11-05 01:46                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-05 13:36                                                             ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-11-06 06:38                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-06 09:02                                                                 ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
2013-11-06 09:23                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-06 09:55                                                                     ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
2013-11-06 18:14                                                                   ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-11-06 19:12                                                                 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-11-07 10:11                                                                   ` Re: [v9.4] row level security Dean Rasheed <dean.a.rasheed@gmail.com>
2013-11-08 02:17                                                                     ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-07 13:47                                                                 ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
2013-11-08 02:08                                                                   ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-11 21:40                                                                     ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-12-14 03:24                                                                     ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
2013-12-14 04:40                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-12-18 08:41                                                                         ` Re: [v9.4] row level security Yeb Havinga <yebhavinga@gmail.com>
2014-01-17 19:27                                                                         ` Re: [v9.4] row level security Gregory Smith <gregsmithpgsql@gmail.com>
2014-01-20 01:58                                                                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2014-01-20 06:15                                                                             ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2014-01-24 02:12                                                                               ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2014-01-24 03:15                                                                                 ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-12-14 04:56                                                                       ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-11-11 21:39                                                                 ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-09-04 15:28                                                   ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-04 15:58                                                   ` Re: [v9.4] row level security Tom Lane <tgl@sss.pgh.pa.us>
2013-08-31 05:03                                       ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-31 19:40                                         ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-09-01 18:44                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-01 18:05                                         ` Re: [v9.4] row level security Josh Berkus <josh@agliodbs.com>
2013-09-01 19:01                                           ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-09-01 19:31                                           ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-01 21:54                                             ` Re: [v9.4] row level security Greg Stark <stark@mit.edu>
2013-09-02 03:47                                               ` Re: [v9.4] row level security Greg Smith <greg@2ndQuadrant.com>
2013-09-04 14:46                                                 ` Re: [v9.4] row level security Robert Haas <robertmhaas@gmail.com>
2013-09-04 15:01                                                   ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-09-04 15:10                                               ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-03 00:08                                           ` Re: [v9.4] row level security Bruce Momjian <bruce@momjian.us>
2013-09-04 15:21                                             ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-08-29 17:09                                 ` Re: [v9.4] row level security Stephen Frost <sfrost@snowman.net>
2013-09-01 12:41                           ` Re: [v9.4] row level security Craig Ringer <craig@2ndquadrant.com>
2013-08-28 11:56                     ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-09-07 01:23                       ` Re: [v9.4] row level security Peter Eisentraut <peter_e@gmx.net>
2013-09-22 06:33                         ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-07-20 13:47             ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>
2013-10-10 18:56 Re: [v9.4] row level security Marc Munro <marc@bloodnok.com>
2013-10-16 15:24 ` Re: [v9.4] row level security Kohei KaiGai <kaigai@kaigai.gr.jp>

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox