pgsql-fix-leaky-join-view.8.patch
application/octect-stream
Filename: pgsql-fix-leaky-join-view.8.patch
Type: application/octect-stream
Part: 0
Message:
Re: leaky views, yet again
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| doc/src/sgml/catalogs.sgml | 6 | 0 |
| doc/src/sgml/ref/create_view.sgml | 19 | 0 |
| doc/src/sgml/rules.sgml | 38 | 0 |
| src/backend/access/common/reloptions.c | 43 | 0 |
| src/backend/catalog/system_views.sql | 2 | 0 |
| src/backend/commands/tablecmds.c | 11 | 0 |
| src/backend/commands/view.c | 28 | 0 |
| src/backend/nodes/copyfuncs.c | 10 | 0 |
| src/backend/nodes/equalfuncs.c | 10 | 0 |
| src/backend/nodes/outfuncs.c | 10 | 0 |
| src/backend/nodes/readfuncs.c | 10 | 0 |
| src/backend/optimizer/path/costsize.c | 17 | 0 |
| src/backend/optimizer/plan/createplan.c | 5 | 0 |
| src/backend/optimizer/plan/initsplan.c | 76 | 0 |
| src/backend/optimizer/prep/prepjointree.c | 6 | 0 |
| src/backend/optimizer/util/clauses.c | 131 | 0 |
| src/backend/parser/gram.y | 18 | 0 |
| src/backend/rewrite/rewriteHandler.c | 121 | 0 |
| src/backend/utils/cache/lsyscache.c | 19 | 0 |
| src/backend/utils/cache/relcache.c | 1 | 0 |
| src/bin/pg_dump/pg_dump.c | 8 | 0 |
| src/bin/psql/describe.c | 10 | 0 |
| src/include/access/reloptions.h | 2 | 0 |
| src/include/nodes/parsenodes.h | 3 | 0 |
| src/include/nodes/primnodes.h | 7 | 0 |
| src/include/nodes/relation.h | 1 | 0 |
| src/include/optimizer/clauses.h | 1 | 0 |
| src/include/utils/lsyscache.h | 1 | 0 |
| src/include/utils/rel.h | 18 | 0 |
| src/test/regress/expected/rules.out | 1 | 0 |
| src/test/regress/expected/security_views.out | 133 | 0 |
| src/test/regress/parallel_schedule | 1 | 0 |
| src/test/regress/serial_schedule | 1 | 0 |
| src/test/regress/sql/security_views.sql | 80 | 0 |
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7763,7768 ****
--- 7763,7774 ----
<entry>Name of view's owner</entry>
</row>
<row>
+ <entry><structfield>security</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>True, if this view has <literal>SECURITY VIEW</literal> attribute</entry>
+ </row>
+ <row>
<entry><structfield>definition</structfield></entry>
<entry><type>text</type></entry>
<entry></entry>
*** a/doc/src/sgml/ref/create_view.sgml
--- b/doc/src/sgml/ref/create_view.sgml
***************
*** 21,27 **** PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
! CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
AS <replaceable class="PARAMETER">query</replaceable>
</synopsis>
</refsynopsisdiv>
--- 21,27 ----
<refsynopsisdiv>
<synopsis>
! CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] [ SECURITY ] VIEW <replaceable class="PARAMETER">name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ]
AS <replaceable class="PARAMETER">query</replaceable>
</synopsis>
</refsynopsisdiv>
***************
*** 80,85 **** CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
--- 80,103 ----
</varlistentry>
<varlistentry>
+ <term><literal>SECURITY</literal></term>
+ <listitem>
+ <para>
+ If specified, the view is created with a security flag that inhibits
+ a part of query optimization to prevent unexpected data leaks
+ using supplied functions on <literal>WHERE</literal> clause.
+
+ If and when we put a function having a side-effect (such as raising
+ a notice message containing its arguments) on <literal>WHERE</literal>
+ clause, it may allows users to see contents of tuples to be filtered
+ out by the view.
+
+ Also see <xref linkend="rules-privileges"> for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><replaceable class="parameter">name</replaceable></term>
<listitem>
<para>
*** a/doc/src/sgml/rules.sgml
--- b/doc/src/sgml/rules.sgml
***************
*** 1856,1861 **** SELECT * FROM phone_number WHERE tricky(person, phone);
--- 1856,1899 ----
</para>
<para>
+ Here is one other scenario for insecure view.
+ <programlisting>
+ CREATE VIEW self_phone_number AS
+ SELECT * FROM person_data l JOIN phone_data r ON l.id = r.person_id
+ WHERE person_account = getpgusername();
+ GRANT SELECT ON self_phone_number TO public;
+ </programlisting>
+ The owner of this view intend to prevent leaking phone numbers which
+ are not associated with the current database account, but it does not
+ perform as he expected, because the optimizer tries to assign the
+ supplied condition on minimum set of scan nodes as they can possibile.
+ <programlisting>
+ SELECT * FROM self_phone_number WHERE tricky(person_id, phone);
+ </programlisting>
+ Again, we use the <function>tricky()</function> function.
+ It references only columns within <literal>phone_data</literal> table,
+ so the optimizer tries to assign this clause on the scan plan on right
+ side of the join.
+ On the other hand, the comparizon of <literal>person_account</literal>
+ and <function>getpgusername()</function> depends on left side of the
+ join, so the optimizer tries to assign this clause on the scan plan of
+ the <literal>person_data</literal>.
+
+ It is not a wrong decision from the viewpoint of performance, but
+ it may provide <function>tricky()</> content of tuples to be filtered
+ out because of the condition within the view definition.
+ </para>
+
+ <para>
+ We can create a view with a security flag that inhibits a part of query
+ optimization. It enables to keep function evaluation orders.
+ If specified, all the supplied clauses shall be evaluated after all
+ the functions within view definition are checked.
+
+ For more details, see <xref linkend="SQL-CREATEVIEW">
+ </para>
+
+ <para>
Similar considerations apply to update rules. In the examples of
the previous section, the owner of the tables in the example
database could grant the privileges <literal>SELECT</>,
*** a/src/backend/access/common/reloptions.c
--- b/src/backend/access/common/reloptions.c
***************
*** 66,71 **** static relopt_bool boolRelOpts[] =
--- 66,79 ----
},
true
},
+ {
+ {
+ "security_view",
+ "Prevent maximum optimization to prevent data leaks",
+ RELOPT_KIND_VIEW
+ },
+ false
+ },
/* list terminator */
{{NULL}}
};
***************
*** 777,782 **** extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, Oid amoptions)
--- 785,791 ----
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_UNCATALOGED:
+ case RELKIND_VIEW:
options = heap_reloptions(classForm->relkind, datum, false);
break;
case RELKIND_INDEX:
***************
*** 1131,1137 **** default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_factor)},
{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
! offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)}
};
options = parseRelOptions(reloptions, validate, kind, &numoptions);
--- 1140,1146 ----
{"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_factor)},
{"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
! offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_scale_factor)},
};
options = parseRelOptions(reloptions, validate, kind, &numoptions);
***************
*** 1151,1156 **** default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
--- 1160,1195 ----
}
/*
+ * Options parser for StdViewOptions
+ */
+ bytea *
+ default_viewoptions(Datum reloptions, bool validate, relopt_kind kind)
+ {
+ relopt_value *options;
+ StdViewOptions *viewopts;
+ int numopts;
+ static const relopt_parse_elt tab[] = {
+ {"security_view", RELOPT_TYPE_BOOL,
+ offsetof(StdViewOptions, security_view)},
+ };
+
+ options = parseRelOptions(reloptions, validate, kind, &numopts);
+
+ /* if none set, we're done */
+ if (numopts == 0)
+ return NULL;
+
+ viewopts = palloc0(sizeof(StdViewOptions));
+
+ fillRelOptions((void *) viewopts, sizeof(StdViewOptions),
+ options, numopts, validate, tab, lengthof(tab));
+
+ pfree(options);
+
+ return (bytea *)viewopts;
+ }
+
+ /*
* Parse options for heaps and toast tables.
*/
bytea *
***************
*** 1173,1180 **** heap_reloptions(char relkind, Datum reloptions, bool validate)
return (bytea *) rdopts;
case RELKIND_RELATION:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
default:
! /* sequences, composite types and views are not supported */
return NULL;
}
}
--- 1212,1221 ----
return (bytea *) rdopts;
case RELKIND_RELATION:
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
+ case RELKIND_VIEW:
+ return default_viewoptions(reloptions, validate, RELOPT_KIND_VIEW);
default:
! /* sequences and composite types are not supported */
return NULL;
}
}
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
***************
*** 74,79 **** CREATE VIEW pg_views AS
--- 74,81 ----
N.nspname AS schemaname,
C.relname AS viewname,
pg_get_userbyid(C.relowner) AS viewowner,
+ C.reloptions IS NOT NULL
+ and '{security_view=1}' <@ C.reloptions AS security,
pg_get_viewdef(C.oid) AS definition
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE C.relkind = 'v';
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 264,270 **** static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, bool allowView, bool allowType);
! static void ATSimplePermissionsRelationOrIndex(Relation rel);
static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
static void ATOneLevelRecursion(List **wqueue, Relation rel,
--- 264,270 ----
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, bool allowView, bool allowType);
! static void ATSimplePermissionsRelationOrIndex(Relation rel, bool allowView);
static void ATSimpleRecursion(List **wqueue, Relation rel,
AlterTableCmd *cmd, bool recurse, LOCKMODE lockmode);
static void ATOneLevelRecursion(List **wqueue, Relation rel,
***************
*** 2702,2708 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
! ATSimplePermissionsRelationOrIndex(rel);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
--- 2702,2708 ----
break;
case AT_SetOptions: /* ALTER COLUMN SET ( options ) */
case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */
! ATSimplePermissionsRelationOrIndex(rel, false);
/* This command never recurses */
pass = AT_PASS_MISC;
break;
***************
*** 2780,2793 **** ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
! ATSimplePermissionsRelationOrIndex(rel);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
break;
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
! ATSimplePermissionsRelationOrIndex(rel);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
--- 2780,2793 ----
pass = AT_PASS_DROP;
break;
case AT_SetTableSpace: /* SET TABLESPACE */
! ATSimplePermissionsRelationOrIndex(rel, false);
/* This command never recurses */
ATPrepSetTableSpace(tab, rel, cmd->name, lockmode);
pass = AT_PASS_MISC; /* doesn't actually matter */
break;
case AT_SetRelOptions: /* SET (...) */
case AT_ResetRelOptions: /* RESET (...) */
! ATSimplePermissionsRelationOrIndex(rel, true);
/* This command never recurses */
/* No command-specific prep needed */
pass = AT_PASS_MISC;
***************
*** 3584,3597 **** ATSimplePermissions(Relation rel, bool allowView, bool allowType)
* - Ensure that it is not a system table
*/
static void
! ATSimplePermissionsRelationOrIndex(Relation rel)
{
if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table or index",
! RelationGetRelationName(rel))));
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
--- 3584,3599 ----
* - Ensure that it is not a system table
*/
static void
! ATSimplePermissionsRelationOrIndex(Relation rel, bool allowView)
{
if (rel->rd_rel->relkind != RELKIND_RELATION &&
! rel->rd_rel->relkind != RELKIND_INDEX &&
! (!allowView || rel->rd_rel->relkind != RELKIND_VIEW))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! errmsg("\"%s\" is not a table%s or index",
! RelationGetRelationName(rel),
! allowView ? ", view" : "")));
/* Permissions checks */
if (!pg_class_ownercheck(RelationGetRelid(rel), GetUserId()))
***************
*** 7115,7120 **** ATExecSetRelOptions(Relation rel, List *defList, bool isReset, LOCKMODE lockmode
--- 7117,7123 ----
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
+ case RELKIND_VIEW:
(void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
break;
case RELKIND_INDEX:
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 97,103 **** isViewOnTempTable_walker(Node *node, void *context)
*---------------------------------------------------------------------
*/
static Oid
! DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
{
Oid viewOid,
namespaceId;
--- 97,104 ----
*---------------------------------------------------------------------
*/
static Oid
! DefineVirtualRelation(const RangeVar *relation, List *tlist,
! bool replace, bool security)
{
Oid viewOid,
namespaceId;
***************
*** 148,153 **** DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
--- 149,156 ----
{
Relation rel;
TupleDesc descriptor;
+ List *atcmds = NIL;
+ AlterTableCmd *atcmd;
/*
* Yes. Get exclusive lock on the existing view ...
***************
*** 186,205 **** DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
checkViewTupleDesc(descriptor, rel->rd_att);
/*
* If new attributes have been added, we must add pg_attribute entries
* for them. It is convenient (although overkill) to use the ALTER
* TABLE ADD COLUMN infrastructure for this.
*/
if (list_length(attrList) > rel->rd_att->natts)
{
- List *atcmds = NIL;
ListCell *c;
int skip = rel->rd_att->natts;
foreach(c, attrList)
{
- AlterTableCmd *atcmd;
-
if (skip > 0)
{
skip--;
--- 189,221 ----
checkViewTupleDesc(descriptor, rel->rd_att);
/*
+ * If SECURITY VIEW attribute is changed, we also must update
+ * corresponding reloptions.
+ */
+ if (ViewGetSecurityFlag(rel) != security)
+ {
+ Node *node;
+
+ node = (Node *)makeInteger(security);
+ node = (Node *)makeDefElem("security_view", node);
+ atcmd = makeNode(AlterTableCmd);
+ atcmd->subtype = AT_SetRelOptions;
+ atcmd->def = (Node *)list_make1(node);
+ atcmds = lappend(atcmds, atcmd);
+ }
+
+ /*
* If new attributes have been added, we must add pg_attribute entries
* for them. It is convenient (although overkill) to use the ALTER
* TABLE ADD COLUMN infrastructure for this.
*/
if (list_length(attrList) > rel->rd_att->natts)
{
ListCell *c;
int skip = rel->rd_att->natts;
foreach(c, attrList)
{
if (skip > 0)
{
skip--;
***************
*** 210,217 **** DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
atcmd->def = (Node *) lfirst(c);
atcmds = lappend(atcmds, atcmd);
}
- AlterTableInternal(viewOid, atcmds, true);
}
/*
* Seems okay, so return the OID of the pre-existing view.
--- 226,234 ----
atcmd->def = (Node *) lfirst(c);
atcmds = lappend(atcmds, atcmd);
}
}
+ if (atcmds != NIL)
+ AlterTableInternal(viewOid, atcmds, true);
/*
* Seems okay, so return the OID of the pre-existing view.
***************
*** 237,242 **** DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
--- 254,264 ----
createStmt->tablespacename = NULL;
createStmt->if_not_exists = false;
+ if (security)
+ createStmt->options = lappend(createStmt->options,
+ makeDefElem("security_view",
+ (Node *) makeInteger(true)));
+
/*
* finally create the relation (this will error out if there's an
* existing view, so we don't need more code to complain if "replace"
***************
*** 470,476 **** DefineView(ViewStmt *stmt, const char *queryString)
* aborted.
*/
viewOid = DefineVirtualRelation(view, viewParse->targetList,
! stmt->replace);
/*
* The relation we have just created is not visible to any other commands
--- 492,498 ----
* aborted.
*/
viewOid = DefineVirtualRelation(view, viewParse->targetList,
! stmt->replace, stmt->security);
/*
* The relation we have just created is not visible to any other commands
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 1107,1112 **** _copyFuncExpr(FuncExpr *from)
--- 1107,1113 ----
COPY_SCALAR_FIELD(funcformat);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1141,1146 **** _copyOpExpr(OpExpr *from)
--- 1142,1148 ----
COPY_SCALAR_FIELD(opretset);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1159,1164 **** _copyDistinctExpr(DistinctExpr *from)
--- 1161,1167 ----
COPY_SCALAR_FIELD(opretset);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1176,1181 **** _copyScalarArrayOpExpr(ScalarArrayOpExpr *from)
--- 1179,1185 ----
COPY_SCALAR_FIELD(useOr);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1312,1317 **** _copyCoerceViaIO(CoerceViaIO *from)
--- 1316,1322 ----
COPY_SCALAR_FIELD(resulttype);
COPY_SCALAR_FIELD(coerceformat);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1331,1336 **** _copyArrayCoerceExpr(ArrayCoerceExpr *from)
--- 1336,1342 ----
COPY_SCALAR_FIELD(isExplicit);
COPY_SCALAR_FIELD(coerceformat);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1444,1449 **** _copyRowCompareExpr(RowCompareExpr *from)
--- 1450,1456 ----
COPY_NODE_FIELD(opfamilies);
COPY_NODE_FIELD(largs);
COPY_NODE_FIELD(rargs);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1514,1519 **** _copyNullIfExpr(NullIfExpr *from)
--- 1521,1527 ----
COPY_SCALAR_FIELD(opretset);
COPY_NODE_FIELD(args);
COPY_LOCATION_FIELD(location);
+ COPY_SCALAR_FIELD(depth);
return newnode;
}
***************
*** 1671,1676 **** _copyFromExpr(FromExpr *from)
--- 1679,1685 ----
COPY_NODE_FIELD(fromlist);
COPY_NODE_FIELD(quals);
+ COPY_SCALAR_FIELD(security_view);
return newnode;
}
***************
*** 1825,1830 **** _copyRangeTblEntry(RangeTblEntry *from)
--- 1834,1840 ----
COPY_SCALAR_FIELD(rtekind);
COPY_SCALAR_FIELD(relid);
COPY_NODE_FIELD(subquery);
+ COPY_SCALAR_FIELD(security_view);
COPY_SCALAR_FIELD(jointype);
COPY_NODE_FIELD(joinaliasvars);
COPY_NODE_FIELD(funcexpr);
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 238,243 **** _equalFuncExpr(FuncExpr *a, FuncExpr *b)
--- 238,244 ----
COMPARE_NODE_FIELD(args);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 273,278 **** _equalOpExpr(OpExpr *a, OpExpr *b)
--- 274,280 ----
COMPARE_SCALAR_FIELD(opretset);
COMPARE_NODE_FIELD(args);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 297,302 **** _equalDistinctExpr(DistinctExpr *a, DistinctExpr *b)
--- 299,305 ----
COMPARE_SCALAR_FIELD(opretset);
COMPARE_NODE_FIELD(args);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 320,325 **** _equalScalarArrayOpExpr(ScalarArrayOpExpr *a, ScalarArrayOpExpr *b)
--- 323,329 ----
COMPARE_SCALAR_FIELD(useOr);
COMPARE_NODE_FIELD(args);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 434,439 **** _equalCoerceViaIO(CoerceViaIO *a, CoerceViaIO *b)
--- 438,444 ----
return false;
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 457,462 **** _equalArrayCoerceExpr(ArrayCoerceExpr *a, ArrayCoerceExpr *b)
--- 462,468 ----
return false;
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 553,558 **** _equalRowCompareExpr(RowCompareExpr *a, RowCompareExpr *b)
--- 559,565 ----
COMPARE_NODE_FIELD(opfamilies);
COMPARE_NODE_FIELD(largs);
COMPARE_NODE_FIELD(rargs);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 614,619 **** _equalNullIfExpr(NullIfExpr *a, NullIfExpr *b)
--- 621,627 ----
COMPARE_SCALAR_FIELD(opretset);
COMPARE_NODE_FIELD(args);
COMPARE_LOCATION_FIELD(location);
+ COMPARE_SCALAR_FIELD(depth);
return true;
}
***************
*** 730,735 **** _equalFromExpr(FromExpr *a, FromExpr *b)
--- 738,744 ----
{
COMPARE_NODE_FIELD(fromlist);
COMPARE_NODE_FIELD(quals);
+ COMPARE_SCALAR_FIELD(security_view);
return true;
}
***************
*** 2183,2188 **** _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b)
--- 2192,2198 ----
COMPARE_SCALAR_FIELD(rtekind);
COMPARE_SCALAR_FIELD(relid);
COMPARE_NODE_FIELD(subquery);
+ COMPARE_SCALAR_FIELD(security_view);
COMPARE_SCALAR_FIELD(jointype);
COMPARE_NODE_FIELD(joinaliasvars);
COMPARE_NODE_FIELD(funcexpr);
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 930,935 **** _outFuncExpr(StringInfo str, FuncExpr *node)
--- 930,936 ----
WRITE_ENUM_FIELD(funcformat, CoercionForm);
WRITE_NODE_FIELD(args);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 954,959 **** _outOpExpr(StringInfo str, OpExpr *node)
--- 955,961 ----
WRITE_BOOL_FIELD(opretset);
WRITE_NODE_FIELD(args);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 967,972 **** _outDistinctExpr(StringInfo str, DistinctExpr *node)
--- 969,975 ----
WRITE_BOOL_FIELD(opretset);
WRITE_NODE_FIELD(args);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 979,984 **** _outScalarArrayOpExpr(StringInfo str, ScalarArrayOpExpr *node)
--- 982,988 ----
WRITE_BOOL_FIELD(useOr);
WRITE_NODE_FIELD(args);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 1092,1097 **** _outCoerceViaIO(StringInfo str, CoerceViaIO *node)
--- 1096,1102 ----
WRITE_OID_FIELD(resulttype);
WRITE_ENUM_FIELD(coerceformat, CoercionForm);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 1106,1111 **** _outArrayCoerceExpr(StringInfo str, ArrayCoerceExpr *node)
--- 1111,1117 ----
WRITE_BOOL_FIELD(isExplicit);
WRITE_ENUM_FIELD(coerceformat, CoercionForm);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 1184,1189 **** _outRowCompareExpr(StringInfo str, RowCompareExpr *node)
--- 1190,1196 ----
WRITE_NODE_FIELD(opfamilies);
WRITE_NODE_FIELD(largs);
WRITE_NODE_FIELD(rargs);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 1234,1239 **** _outNullIfExpr(StringInfo str, NullIfExpr *node)
--- 1241,1247 ----
WRITE_BOOL_FIELD(opretset);
WRITE_NODE_FIELD(args);
WRITE_LOCATION_FIELD(location);
+ WRITE_INT_FIELD(depth);
}
static void
***************
*** 1341,1346 **** _outFromExpr(StringInfo str, FromExpr *node)
--- 1349,1355 ----
WRITE_NODE_FIELD(fromlist);
WRITE_NODE_FIELD(quals);
+ WRITE_BOOL_FIELD(security_view);
}
/*****************************************************************************
***************
*** 2124,2129 **** _outRangeTblEntry(StringInfo str, RangeTblEntry *node)
--- 2133,2139 ----
break;
case RTE_SUBQUERY:
WRITE_NODE_FIELD(subquery);
+ WRITE_BOOL_FIELD(security_view);
break;
case RTE_JOIN:
WRITE_ENUM_FIELD(jointype, JoinType);
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 526,531 **** _readFuncExpr(void)
--- 526,532 ----
READ_ENUM_FIELD(funcformat, CoercionForm);
READ_NODE_FIELD(args);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 571,576 **** _readOpExpr(void)
--- 572,578 ----
READ_BOOL_FIELD(opretset);
READ_NODE_FIELD(args);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 600,605 **** _readDistinctExpr(void)
--- 602,608 ----
READ_BOOL_FIELD(opretset);
READ_NODE_FIELD(args);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 628,633 **** _readScalarArrayOpExpr(void)
--- 631,637 ----
READ_BOOL_FIELD(useOr);
READ_NODE_FIELD(args);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 740,745 **** _readCoerceViaIO(void)
--- 744,750 ----
READ_OID_FIELD(resulttype);
READ_ENUM_FIELD(coerceformat, CoercionForm);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 759,764 **** _readArrayCoerceExpr(void)
--- 764,770 ----
READ_BOOL_FIELD(isExplicit);
READ_ENUM_FIELD(coerceformat, CoercionForm);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 872,877 **** _readRowCompareExpr(void)
--- 878,884 ----
READ_NODE_FIELD(opfamilies);
READ_NODE_FIELD(largs);
READ_NODE_FIELD(rargs);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 953,958 **** _readNullIfExpr(void)
--- 960,966 ----
READ_BOOL_FIELD(opretset);
READ_NODE_FIELD(args);
READ_LOCATION_FIELD(location);
+ READ_INT_FIELD(depth);
READ_DONE();
}
***************
*** 1110,1115 **** _readFromExpr(void)
--- 1118,1124 ----
READ_NODE_FIELD(fromlist);
READ_NODE_FIELD(quals);
+ READ_BOOL_FIELD(security_view);
READ_DONE();
}
***************
*** 1140,1145 **** _readRangeTblEntry(void)
--- 1149,1155 ----
break;
case RTE_SUBQUERY:
READ_NODE_FIELD(subquery);
+ READ_BOOL_FIELD(security_view);
break;
case RTE_JOIN:
READ_ENUM_FIELD(jointype, JoinType);
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
***************
*** 2467,2472 **** cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root)
--- 2467,2473 ----
context.root = root;
context.total.startup = 0;
context.total.per_tuple = 0;
+ context.total.depth = 0;
/* We don't charge any cost for the implicit ANDing at top level ... */
***************
*** 2492,2497 **** cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
--- 2493,2499 ----
context.root = root;
context.total.startup = 0;
context.total.per_tuple = 0;
+ context.total.depth = 0;
cost_qual_eval_walker(qual, &context);
***************
*** 2521,2526 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2523,2529 ----
locContext.root = context->root;
locContext.total.startup = 0;
locContext.total.per_tuple = 0;
+ locContext.total.depth = 0;
/*
* For an OR clause, recurse into the marked-up tree so that we
***************
*** 2545,2550 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2548,2555 ----
}
context->total.startup += rinfo->eval_cost.startup;
context->total.per_tuple += rinfo->eval_cost.per_tuple;
+ if (rinfo->eval_cost.depth > context->total.depth)
+ context->total.depth = rinfo->eval_cost.depth;
/* do NOT recurse into children */
return false;
}
***************
*** 2573,2578 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2578,2585 ----
{
context->total.per_tuple +=
get_func_cost(((FuncExpr *) node)->funcid) * cpu_operator_cost;
+ if (((FuncExpr *)node)->depth > context->total.depth)
+ context->total.depth = ((FuncExpr *)node)->depth;
}
else if (IsA(node, OpExpr) ||
IsA(node, DistinctExpr) ||
***************
*** 2582,2587 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2589,2596 ----
set_opfuncid((OpExpr *) node);
context->total.per_tuple +=
get_func_cost(((OpExpr *) node)->opfuncid) * cpu_operator_cost;
+ if (((OpExpr *)node)->depth > context->total.depth)
+ context->total.depth = ((OpExpr *)node)->depth;
}
else if (IsA(node, ScalarArrayOpExpr))
{
***************
*** 2595,2600 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2604,2611 ----
set_sa_opfuncid(saop);
context->total.per_tuple += get_func_cost(saop->opfuncid) *
cpu_operator_cost * estimate_array_length(arraynode) * 0.5;
+ if (saop->depth > context->total.depth)
+ context->total.depth = saop->depth;
}
else if (IsA(node, CoerceViaIO))
{
***************
*** 2611,2616 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2622,2629 ----
getTypeOutputInfo(exprType((Node *) iocoerce->arg),
&iofunc, &typisvarlena);
context->total.per_tuple += get_func_cost(iofunc) * cpu_operator_cost;
+ if (iocoerce->depth > context->total.depth)
+ context->total.depth = iocoerce->depth;
}
else if (IsA(node, ArrayCoerceExpr))
{
***************
*** 2620,2625 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2633,2640 ----
if (OidIsValid(acoerce->elemfuncid))
context->total.per_tuple += get_func_cost(acoerce->elemfuncid) *
cpu_operator_cost * estimate_array_length(arraynode);
+ if (acoerce->depth > context->total.depth)
+ context->total.depth = acoerce->depth;
}
else if (IsA(node, RowCompareExpr))
{
***************
*** 2634,2639 **** cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
--- 2649,2656 ----
context->total.per_tuple += get_func_cost(get_opcode(opid)) *
cpu_operator_cost;
}
+ if (rcexpr->depth > context->total.depth)
+ context->total.depth = rcexpr->depth;
}
else if (IsA(node, CurrentOfExpr))
{
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 2444,2449 **** order_qual_clauses(PlannerInfo *root, List *clauses)
--- 2444,2450 ----
{
Node *clause;
Cost cost;
+ int depth;
} QualItem;
int nitems = list_length(clauses);
QualItem *items;
***************
*** 2469,2474 **** order_qual_clauses(PlannerInfo *root, List *clauses)
--- 2470,2476 ----
cost_qual_eval_node(&qcost, clause, root);
items[i].clause = clause;
items[i].cost = qcost.per_tuple;
+ items[i].depth = qcost.depth;
i++;
}
***************
*** 2485,2491 **** order_qual_clauses(PlannerInfo *root, List *clauses)
/* insert newitem into the already-sorted subarray */
for (j = i; j > 0; j--)
{
! if (newitem.cost >= items[j - 1].cost)
break;
items[j] = items[j - 1];
}
--- 2487,2495 ----
/* insert newitem into the already-sorted subarray */
for (j = i; j > 0; j--)
{
! if (newitem.depth < items[j - 1].depth ||
! (newitem.depth == items[j - 1].depth &&
! newitem.cost >= items[j - 1].cost))
break;
items[j] = items[j - 1];
}
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 40,46 **** int join_collapse_limit;
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
bool below_outer_join,
! Relids *qualscope, Relids *inner_join_rels);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
--- 40,47 ----
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
bool below_outer_join,
! Relids *qualscope, Relids *inner_join_rels,
! bool below_sec_barriers, Relids *sec_barriers);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
Relids left_rels, Relids right_rels,
Relids inner_join_rels,
***************
*** 51,57 **** static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
JoinType jointype,
Relids qualscope,
Relids ojscope,
! Relids outerjoin_nonnullable);
static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
Relids *nullable_relids_p, bool is_pushed_down);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
--- 52,59 ----
JoinType jointype,
Relids qualscope,
Relids ojscope,
! Relids outerjoin_nonnullable,
! Relids sec_barriers);
static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p,
Relids *nullable_relids_p, bool is_pushed_down);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
***************
*** 238,250 **** deconstruct_jointree(PlannerInfo *root)
{
Relids qualscope;
Relids inner_join_rels;
/* Start recursion at top of jointree */
! Assert(root->parse->jointree != NULL &&
! IsA(root->parse->jointree, FromExpr));
! return deconstruct_recurse(root, (Node *) root->parse->jointree, false,
! &qualscope, &inner_join_rels);
}
/*
--- 240,254 ----
{
Relids qualscope;
Relids inner_join_rels;
+ Relids sec_barriers;
+ FromExpr *f = (FromExpr *)root->parse->jointree;
/* Start recursion at top of jointree */
! Assert(root->parse->jointree != NULL && IsA(f, FromExpr));
! return deconstruct_recurse(root, (Node *) f, false,
! &qualscope, &inner_join_rels,
! f->security_view, &sec_barriers);
}
/*
***************
*** 268,274 **** deconstruct_jointree(PlannerInfo *root)
*/
static List *
deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
! Relids *qualscope, Relids *inner_join_rels)
{
List *joinlist;
--- 272,279 ----
*/
static List *
deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
! Relids *qualscope, Relids *inner_join_rels,
! bool below_sec_barriers, Relids *sec_barriers)
{
List *joinlist;
***************
*** 287,292 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
--- 292,300 ----
/* A single baserel does not create an inner join */
*inner_join_rels = NULL;
joinlist = list_make1(jtnode);
+ /* Is it in security barrier? */
+ *sec_barriers = (below_sec_barriers ?
+ bms_make_singleton(varno) : NULL);
}
else if (IsA(jtnode, FromExpr))
{
***************
*** 302,307 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
--- 310,316 ----
*/
*qualscope = NULL;
*inner_join_rels = NULL;
+ *sec_barriers = NULL;
joinlist = NIL;
remaining = list_length(f->fromlist);
foreach(l, f->fromlist)
***************
*** 309,320 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
Relids sub_qualscope;
List *sub_joinlist;
int sub_members;
sub_joinlist = deconstruct_recurse(root, lfirst(l),
below_outer_join,
&sub_qualscope,
! inner_join_rels);
*qualscope = bms_add_members(*qualscope, sub_qualscope);
sub_members = list_length(sub_joinlist);
remaining--;
if (sub_members <= 1 ||
--- 318,334 ----
Relids sub_qualscope;
List *sub_joinlist;
int sub_members;
+ Relids sub_barriers;
sub_joinlist = deconstruct_recurse(root, lfirst(l),
below_outer_join,
&sub_qualscope,
! inner_join_rels,
! below_sec_barriers ?
! true : f->security_view,
! &sub_barriers);
*qualscope = bms_add_members(*qualscope, sub_qualscope);
+ *sec_barriers = bms_add_members(*sec_barriers, sub_barriers);
sub_members = list_length(sub_joinlist);
remaining--;
if (sub_members <= 1 ||
***************
*** 343,349 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
distribute_qual_to_rels(root, qual,
false, below_outer_join, JOIN_INNER,
! *qualscope, NULL, NULL);
}
}
else if (IsA(jtnode, JoinExpr))
--- 357,363 ----
distribute_qual_to_rels(root, qual,
false, below_outer_join, JOIN_INNER,
! *qualscope, NULL, NULL, *sec_barriers);
}
}
else if (IsA(jtnode, JoinExpr))
***************
*** 353,358 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
--- 367,374 ----
rightids,
left_inners,
right_inners,
+ left_barriers,
+ right_barriers,
nonnullable_rels,
ojscope;
List *leftjoinlist,
***************
*** 377,388 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
case JOIN_INNER:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
! &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = *qualscope;
/* Inner join adds no restrictions for quals */
nonnullable_rels = NULL;
break;
--- 393,409 ----
case JOIN_INNER:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners,
! below_sec_barriers,
! &left_barriers);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
! &rightids, &right_inners,
! below_sec_barriers,
! &right_barriers);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = *qualscope;
+ *sec_barriers = bms_union(left_barriers, right_barriers);
/* Inner join adds no restrictions for quals */
nonnullable_rels = NULL;
break;
***************
*** 390,424 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
case JOIN_ANTI:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
! &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
nonnullable_rels = leftids;
break;
case JOIN_SEMI:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
! &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
/* Semi join adds no restrictions for quals */
nonnullable_rels = NULL;
break;
case JOIN_FULL:
leftjoinlist = deconstruct_recurse(root, j->larg,
true,
! &leftids, &left_inners);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
! &rightids, &right_inners);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
/* each side is both outer and inner */
nonnullable_rels = *qualscope;
break;
--- 411,460 ----
case JOIN_ANTI:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners,
! below_sec_barriers,
! &left_barriers);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
! &rightids, &right_inners,
! below_sec_barriers,
! &right_barriers);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
+ *sec_barriers = bms_union(left_barriers, right_barriers);
nonnullable_rels = leftids;
break;
case JOIN_SEMI:
leftjoinlist = deconstruct_recurse(root, j->larg,
below_outer_join,
! &leftids, &left_inners,
! below_sec_barriers,
! &left_barriers);
rightjoinlist = deconstruct_recurse(root, j->rarg,
below_outer_join,
! &rightids, &right_inners,
! below_sec_barriers,
! &right_barriers);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
+ *sec_barriers = bms_union(left_barriers, right_barriers);
/* Semi join adds no restrictions for quals */
nonnullable_rels = NULL;
break;
case JOIN_FULL:
leftjoinlist = deconstruct_recurse(root, j->larg,
true,
! &leftids, &left_inners,
! below_sec_barriers,
! &left_barriers);
rightjoinlist = deconstruct_recurse(root, j->rarg,
true,
! &rightids, &right_inners,
! below_sec_barriers,
! &right_barriers);
*qualscope = bms_union(leftids, rightids);
*inner_join_rels = bms_union(left_inners, right_inners);
+ *sec_barriers = bms_union(left_barriers, right_barriers);
/* each side is both outer and inner */
nonnullable_rels = *qualscope;
break;
***************
*** 467,473 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
distribute_qual_to_rels(root, qual,
false, below_outer_join, j->jointype,
*qualscope,
! ojscope, nonnullable_rels);
}
/* Now we can add the SpecialJoinInfo to join_info_list */
--- 503,510 ----
distribute_qual_to_rels(root, qual,
false, below_outer_join, j->jointype,
*qualscope,
! ojscope, nonnullable_rels,
! *sec_barriers);
}
/* Now we can add the SpecialJoinInfo to join_info_list */
***************
*** 791,797 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
JoinType jointype,
Relids qualscope,
Relids ojscope,
! Relids outerjoin_nonnullable)
{
Relids relids;
bool is_pushed_down;
--- 828,835 ----
JoinType jointype,
Relids qualscope,
Relids ojscope,
! Relids outerjoin_nonnullable,
! Relids sec_barriers)
{
Relids relids;
bool is_pushed_down;
***************
*** 799,804 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 837,843 ----
bool pseudoconstant = false;
bool maybe_equivalence;
bool maybe_outer_join;
+ bool maybe_leakable_clause = false;
Relids nullable_relids;
RestrictInfo *restrictinfo;
***************
*** 815,820 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 854,861 ----
elog(ERROR, "JOIN qualification cannot refer to other relations");
if (ojscope && !bms_is_subset(relids, ojscope))
elog(ERROR, "JOIN qualification cannot refer to other relations");
+ if (sec_barriers && !bms_is_subset(sec_barriers, qualscope))
+ elog(ERROR, "Bug? incorrect security barriers");
/*
* If the clause is variable-free, our normal heuristic for pushing it
***************
*** 871,876 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
--- 912,931 ----
}
}
+ /*
+ * If the clause contains a leakable function, it may be used to
+ * bypass row-level security using views. In this case, we don't
+ * push down the clause not to evaluate the leakable clause prior
+ * to the row-level policy functions.
+ */
+ if (!bms_is_empty(sec_barriers) &&
+ contain_leakable_functions(clause) &&
+ bms_overlap(relids, sec_barriers))
+ {
+ maybe_leakable_clause = true;
+ relids = bms_add_members(relids, sec_barriers);
+ }
+
/*----------
* Check to see if clause application must be delayed by outer-join
* considerations.
***************
*** 1067,1073 **** distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* If none of the above hold, pass it off to
* distribute_restrictinfo_to_rels().
*/
! if (restrictinfo->mergeopfamilies)
{
if (maybe_equivalence)
{
--- 1122,1128 ----
* If none of the above hold, pass it off to
* distribute_restrictinfo_to_rels().
*/
! if (!maybe_leakable_clause && restrictinfo->mergeopfamilies)
{
if (maybe_equivalence)
{
***************
*** 1396,1402 **** process_implied_equality(PlannerInfo *root,
*/
distribute_qual_to_rels(root, (Node *) clause,
true, below_outer_join, JOIN_INNER,
! qualscope, NULL, NULL);
}
/*
--- 1451,1457 ----
*/
distribute_qual_to_rels(root, (Node *) clause,
true, below_outer_join, JOIN_INNER,
! qualscope, NULL, NULL, NULL);
}
/*
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
***************
*** 845,850 **** pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
--- 845,856 ----
*/
/*
+ * If the subquery is originated from security view, we mark it here
+ * to prevent over optimization later.
+ */
+ ((FromExpr *) subquery->jointree)->security_view = rte->security_view;
+
+ /*
* Return the adjusted subquery jointree to replace the RangeTblRef entry
* in parent's jointree.
*/
*** a/src/backend/optimizer/util/clauses.c
--- b/src/backend/optimizer/util/clauses.c
***************
*** 86,91 **** static bool contain_subplans_walker(Node *node, void *context);
--- 86,92 ----
static bool contain_mutable_functions_walker(Node *node, void *context);
static bool contain_volatile_functions_walker(Node *node, void *context);
static bool contain_nonstrict_functions_walker(Node *node, void *context);
+ static bool contain_leakable_functions_walker(Node *node, void *context);
static Relids find_nonnullable_rels_walker(Node *node, bool top_level);
static List *find_nonnullable_vars_walker(Node *node, bool top_level);
static bool is_strict_saop(ScalarArrayOpExpr *expr, bool falseOK);
***************
*** 103,108 **** static Expr *simplify_function(Oid funcid,
--- 104,110 ----
Oid result_type, int32 result_typmod, List **args,
bool has_named_args,
bool allow_inline,
+ int depth,
eval_const_expressions_context *context);
static List *reorder_function_arguments(List *args, Oid result_type,
HeapTuple func_tuple,
***************
*** 115,121 **** static void recheck_cast_function_args(List *args, Oid result_type,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid,
Oid result_type, int32 result_typmod, List *args,
! HeapTuple func_tuple,
eval_const_expressions_context *context);
static Expr *inline_function(Oid funcid, Oid result_type, List *args,
HeapTuple func_tuple,
--- 117,123 ----
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid,
Oid result_type, int32 result_typmod, List *args,
! HeapTuple func_tuple, int depth,
eval_const_expressions_context *context);
static Expr *inline_function(Oid funcid, Oid result_type, List *args,
HeapTuple func_tuple,
***************
*** 1129,1134 **** contain_nonstrict_functions_walker(Node *node, void *context)
--- 1131,1250 ----
}
+
+ /*****************************************************************************
+ * Check clauses for leakable functions
+ *****************************************************************************/
+
+ /*
+ * contain_leakable_functions
+ * Recursively search for leakable functions within a clause.
+ *
+ * Returns true if any function call with side-effect is found.
+ * ie, some type-input/output handler will raise an error when given
+ * argument does not have a valid format.
+ *
+ * When people uses views for row-level security purpose, given qualifiers
+ * come from outside of the view should not be pushed down into the views,
+ * if they have side-effect, because contents of tuples to be filtered out
+ * may be leaked via side-effectable functions within the qualifiers.
+ *
+ * The idea here is that the planner restrain a part of optimization when
+ * the qualifiers contains leakable functions.
+ * This routine checks whether the given clause contains leakable functions,
+ * or not. If we return false, then the clause is clean.
+ */
+ bool
+ contain_leakable_functions(Node *clause)
+ {
+ return contain_leakable_functions_walker(clause, NULL);
+ }
+
+ static bool
+ contain_leakable_functions_walker(Node *node, void *context)
+ {
+ if (node == NULL)
+ return false;
+
+ if (IsA(node, FuncExpr))
+ {
+ /*
+ * currently, we have no way to distinguish a safe function and
+ * a leakable one, so all the function call shall be considered
+ * as leakable one.
+ */
+ return true;
+ }
+ else if (IsA(node, OpExpr))
+ {
+ OpExpr *expr = (OpExpr *) node;
+
+ /*
+ * we assume built-in functions to implement operators are not
+ * leakable, so don't need to prevent optimization.
+ */
+ set_opfuncid(expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, DistinctExpr))
+ {
+ DistinctExpr *expr = (DistinctExpr *) node;
+
+ set_opfuncid((OpExpr *) expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
+
+ set_sa_opfuncid(expr);
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, CoerceViaIO) ||
+ IsA(node, ArrayCoerceExpr))
+ {
+ /*
+ * we assume type-in/out handlers are leakable, even if built-in
+ * functions.
+ * ie, int4in() raises an error message with given argument,
+ * if it does not have valid format for numeric value.
+ */
+ return true;
+ }
+ else if (IsA(node, NullIfExpr))
+ {
+ NullIfExpr *expr = (NullIfExpr *) node;
+
+ set_opfuncid((OpExpr *) expr); /* rely on struct equivalence */
+ if (get_func_lang(expr->opfuncid) != INTERNALlanguageId)
+ return true;
+ /* else fall through to check args */
+ }
+ else if (IsA(node, RowCompareExpr))
+ {
+ /* RowCompare probably can't have volatile ops, but check anyway */
+ RowCompareExpr *rcexpr = (RowCompareExpr *) node;
+ ListCell *opid;
+
+ foreach(opid, rcexpr->opnos)
+ {
+ Oid funcId = get_opcode(lfirst_oid(opid));
+
+ if (get_func_lang(funcId) != INTERNALlanguageId)
+ return true;
+ }
+ /* else fall through to check args */
+ }
+ return expression_tree_walker(node, contain_leakable_functions_walker,
+ context);
+ }
+
/*
* find_nonnullable_rels
* Determine which base rels are forced nonnullable by given clause.
***************
*** 2169,2175 **** eval_const_expressions_mutator(Node *node,
simple = simplify_function(expr->funcid,
expr->funcresulttype, exprTypmod(node),
&args,
! has_named_args, true, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2285,2291 ----
simple = simplify_function(expr->funcid,
expr->funcresulttype, exprTypmod(node),
&args,
! has_named_args, true, expr->depth, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
***************
*** 2186,2191 **** eval_const_expressions_mutator(Node *node,
--- 2302,2308 ----
newexpr->funcformat = expr->funcformat;
newexpr->args = args;
newexpr->location = expr->location;
+ newexpr->depth = expr->depth;
return (Node *) newexpr;
}
if (IsA(node, OpExpr))
***************
*** 2217,2223 **** eval_const_expressions_mutator(Node *node,
simple = simplify_function(expr->opfuncid,
expr->opresulttype, -1,
&args,
! false, true, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
--- 2334,2340 ----
simple = simplify_function(expr->opfuncid,
expr->opresulttype, -1,
&args,
! false, true, expr->depth, context);
if (simple) /* successfully simplified it */
return (Node *) simple;
***************
*** 2246,2251 **** eval_const_expressions_mutator(Node *node,
--- 2363,2369 ----
newexpr->opretset = expr->opretset;
newexpr->args = args;
newexpr->location = expr->location;
+ newexpr->depth = expr->depth;
return (Node *) newexpr;
}
if (IsA(node, DistinctExpr))
***************
*** 2310,2316 **** eval_const_expressions_mutator(Node *node,
simple = simplify_function(expr->opfuncid,
expr->opresulttype, -1,
&args,
! false, false, context);
if (simple) /* successfully simplified it */
{
/*
--- 2428,2434 ----
simple = simplify_function(expr->opfuncid,
expr->opresulttype, -1,
&args,
! false, false, expr->depth, context);
if (simple) /* successfully simplified it */
{
/*
***************
*** 2338,2343 **** eval_const_expressions_mutator(Node *node,
--- 2456,2462 ----
newexpr->opretset = expr->opretset;
newexpr->args = args;
newexpr->location = expr->location;
+ newexpr->depth = expr->depth;
return (Node *) newexpr;
}
if (IsA(node, BoolExpr))
***************
*** 2490,2496 **** eval_const_expressions_mutator(Node *node,
simple = simplify_function(outfunc,
CSTRINGOID, -1,
&args,
! false, true, context);
if (simple) /* successfully simplified output fn */
{
/*
--- 2609,2615 ----
simple = simplify_function(outfunc,
CSTRINGOID, -1,
&args,
! false, true, expr->depth, context);
if (simple) /* successfully simplified output fn */
{
/*
***************
*** 2508,2514 **** eval_const_expressions_mutator(Node *node,
simple = simplify_function(infunc,
expr->resulttype, -1,
&args,
! false, true, context);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
--- 2627,2633 ----
simple = simplify_function(infunc,
expr->resulttype, -1,
&args,
! false, true, expr->depth, context);
if (simple) /* successfully simplified input fn */
return (Node *) simple;
}
***************
*** 2523,2528 **** eval_const_expressions_mutator(Node *node,
--- 2642,2648 ----
newexpr->resulttype = expr->resulttype;
newexpr->coerceformat = expr->coerceformat;
newexpr->location = expr->location;
+ newexpr->depth = expr->depth;
return (Node *) newexpr;
}
if (IsA(node, ArrayCoerceExpr))
***************
*** 2546,2551 **** eval_const_expressions_mutator(Node *node,
--- 2666,2672 ----
newexpr->isExplicit = expr->isExplicit;
newexpr->coerceformat = expr->coerceformat;
newexpr->location = expr->location;
+ newexpr->depth = expr->depth;
/*
* If constant argument and it's a binary-coercible or immutable
***************
*** 3280,3285 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
--- 3401,3407 ----
List **args,
bool has_named_args,
bool allow_inline,
+ int depth,
eval_const_expressions_context *context)
{
HeapTuple func_tuple;
***************
*** 3308,3314 **** simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
*args = add_function_defaults(*args, result_type, func_tuple, context);
newexpr = evaluate_function(funcid, result_type, result_typmod, *args,
! func_tuple, context);
if (!newexpr && allow_inline)
newexpr = inline_function(funcid, result_type, *args,
--- 3430,3436 ----
*args = add_function_defaults(*args, result_type, func_tuple, context);
newexpr = evaluate_function(funcid, result_type, result_typmod, *args,
! func_tuple, depth, context);
if (!newexpr && allow_inline)
newexpr = inline_function(funcid, result_type, *args,
***************
*** 3559,3565 **** recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
*/
static Expr *
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args,
! HeapTuple func_tuple,
eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
--- 3681,3687 ----
*/
static Expr *
evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args,
! HeapTuple func_tuple, int depth,
eval_const_expressions_context *context)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
***************
*** 3642,3647 **** evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, List *args,
--- 3764,3770 ----
newexpr->funcformat = COERCE_DONTCARE; /* doesn't matter */
newexpr->args = args;
newexpr->location = -1;
+ newexpr->depth = depth;
return evaluate_expr((Expr *) newexpr, result_type, result_typmod);
}
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 311,317 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
! %type <boolean> OptTemp opt_trusted opt_restart_seqs
%type <oncommit> OnCommitOption
%type <node> for_locking_item
--- 311,317 ----
%type <fun_param_mode> arg_class
%type <typnam> func_return func_type
! %type <boolean> OptTemp OptSecurity opt_trusted opt_restart_seqs
%type <oncommit> OnCommitOption
%type <node> for_locking_item
***************
*** 6495,6529 **** transaction_mode_list_or_empty:
/*****************************************************************************
*
* QUERY:
! * CREATE [ OR REPLACE ] [ TEMP ] VIEW <viewname> '('target-list ')'
* AS <query> [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
*
*****************************************************************************/
! ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $4;
n->view->istemp = $2;
! n->aliases = $5;
! n->query = $7;
n->replace = false;
$$ = (Node *) n;
}
! | CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $6;
n->view->istemp = $4;
! n->aliases = $7;
! n->query = $9;
n->replace = true;
$$ = (Node *) n;
}
;
opt_check_option:
WITH CHECK OPTION
{
--- 6495,6537 ----
/*****************************************************************************
*
* QUERY:
! * CREATE [ OR REPLACE ] [ TEMP ] [ SECURITY ] VIEW
! * <viewname> '('target-list ')'
* AS <query> [ WITH [ CASCADED | LOCAL ] CHECK OPTION ]
*
*****************************************************************************/
! ViewStmt: CREATE OptTemp OptSecurity VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $5;
n->view->istemp = $2;
! n->aliases = $6;
! n->query = $8;
n->replace = false;
+ n->security = $3;
$$ = (Node *) n;
}
! | CREATE OR REPLACE OptTemp OptSecurity
! VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
! n->view = $7;
n->view->istemp = $4;
! n->aliases = $8;
! n->query = $10;
n->replace = true;
+ n->security = $5;
$$ = (Node *) n;
}
;
+ OptSecurity: SECURITY { $$ = TRUE; }
+ | /* EMPTY */ { $$ = FALSE; }
+ ;
+
opt_check_option:
WITH CHECK OPTION
{
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1218,1223 **** matchLocks(CmdType event,
--- 1218,1337 ----
return matching_locks;
}
+ /*
+ * MarkFuncExprDepth
+ *
+ * It marks the depth field of each expression node that may eventually
+ * call functions. On the checks of WHERE clause, every qualifiers are
+ * sorted by cost estimation, then a qualifier with lower-cost shall
+ * be evaluated ealier than expensive one.
+ * However, this logic can cause a problem when simple subqueries are
+ * pull-up to join clauses. If a view is used to row-level security
+ * purpose, its security policy function should be evaluated earlier
+ * than any functions supplied by users, because they may have side-
+ * effects that leaks given arguments somewhere, alhough it is contents
+ * of tuples to be filtered out.
+ * So, we need to ensure a function originated from inner subquery
+ * being evaluated earlier than functions originated from outer
+ * subqueries.
+ */
+ static bool
+ MarkFuncExprDepthWalker(Node *node, void *context)
+ {
+ int depth = (int) (intptr_t) context;
+
+ if (node == NULL)
+ return false;
+ if (IsA(node, FuncExpr))
+ {
+ FuncExpr *exp = (FuncExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, OpExpr))
+ {
+ OpExpr *exp = (OpExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, DistinctExpr))
+ {
+ DistinctExpr *exp = (DistinctExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, ScalarArrayOpExpr))
+ {
+ ScalarArrayOpExpr *exp = (ScalarArrayOpExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, CoerceViaIO))
+ {
+ CoerceViaIO *exp = (CoerceViaIO *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, ArrayCoerceExpr))
+ {
+ ArrayCoerceExpr *exp = (ArrayCoerceExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, NullIfExpr))
+ {
+ NullIfExpr *exp = (NullIfExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, RowCompareExpr))
+ {
+ RowCompareExpr *exp = (RowCompareExpr *)node;
+
+ exp->depth = depth;
+
+ return false;
+ }
+ else if (IsA(node, Query))
+ {
+ query_tree_walker((Query *)node,
+ MarkFuncExprDepthWalker,
+ (void *) (intptr_t) (depth + 1), 0);
+ return false;
+ }
+ return expression_tree_walker(node, MarkFuncExprDepthWalker, context);
+ }
+
+ static void
+ MarkFuncExprDepth(List *query_list)
+ {
+ ListCell *l;
+
+ foreach (l, query_list)
+ {
+ Node *node = lfirst(l);
+
+ Assert(IsA(node, Query));
+
+ query_tree_walker((Query *)node,
+ MarkFuncExprDepthWalker,
+ (void *) 1, 0);
+ }
+ }
/*
* ApplyRetrieveRule - expand an ON SELECT rule
***************
*** 1334,1339 **** ApplyRetrieveRule(Query *parsetree,
--- 1448,1455 ----
rte->relid = InvalidOid;
rte->subquery = rule_action;
rte->inh = false; /* must not be set for a subquery */
+ if (ViewGetSecurityFlag(relation))
+ rte->security_view = true;
/*
* We move the view's permission check data down to its rangetable. The
***************
*** 2087,2091 **** QueryRewrite(Query *parsetree)
--- 2203,2212 ----
if (!foundOriginalQuery && lastInstead != NULL)
lastInstead->canSetTag = true;
+ /*
+ * Mark the ->depth fields of expression nodes
+ */
+ MarkFuncExprDepth(results);
+
return results;
}
*** a/src/backend/utils/cache/lsyscache.c
--- b/src/backend/utils/cache/lsyscache.c
***************
*** 1275,1280 **** get_func_namespace(Oid funcid)
--- 1275,1299 ----
}
/*
+ * get_func_lang
+ * Given procedure id, return the function's language
+ */
+ Oid
+ get_func_lang(Oid funcid)
+ {
+ HeapTuple tp;
+ Oid result;
+
+ tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ result = ((Form_pg_proc) GETSTRUCT(tp))->prolang;
+ ReleaseSysCache(tp);
+ return result;
+ }
+
+ /*
* get_func_rettype
* Given procedure id, return the function's result type.
*/
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
***************
*** 384,389 **** RelationParseRelOptions(Relation relation, HeapTuple tuple)
--- 384,390 ----
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
+ case RELKIND_VIEW:
break;
default:
return;
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 10882,10890 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
--- 10882,10896 ----
if (tbinfo->relkind == RELKIND_VIEW)
{
char *viewdef;
+ bool secview = false;
reltypename = "VIEW";
+ /* check SECURITY VIEW attribute */
+ if (tbinfo->reloptions &&
+ strstr(tbinfo->reloptions, "security_view=1") != NULL)
+ secview = true;
+
/* Fetch the view definition */
if (g_fout->remoteVersion >= 70300)
{
***************
*** 10936,10942 **** dumpTableSchema(Archive *fout, TableInfo *tbinfo)
if (binary_upgrade)
binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
! appendPQExpBuffer(q, "CREATE VIEW %s AS\n %s\n",
fmtId(tbinfo->dobj.name), viewdef);
PQclear(res);
--- 10942,10949 ----
if (binary_upgrade)
binary_upgrade_set_relfilenodes(q, tbinfo->dobj.catId.oid, false);
! appendPQExpBuffer(q, "CREATE %sVIEW %s AS\n %s\n",
! (secview ? "SECURITY " : ""),
fmtId(tbinfo->dobj.name), viewdef);
PQclear(res);
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 2409,2414 **** listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
--- 2409,2424 ----
appendPQExpBuffer(&buf,
",\n pg_catalog.pg_size_pretty(pg_catalog.pg_relation_size(c.oid)) as \"%s\"",
gettext_noop("Size"));
+ if (verbose && showViews && pset.sversion >= 90100)
+ appendPQExpBuffer(&buf, ",\n"
+ "CASE WHEN c.relkind = 'v'"
+ " THEN CASE WHEN '{security_view=1}' <@ c.reloptions"
+ " THEN 'true'"
+ " ELSE 'false'"
+ " END"
+ " ELSE null"
+ " END AS \"%s\"",
+ gettext_noop("Security"));
if (verbose)
appendPQExpBuffer(&buf,
",\n pg_catalog.obj_description(c.oid, 'pg_class') as \"%s\"",
*** a/src/include/access/reloptions.h
--- b/src/include/access/reloptions.h
***************
*** 42,49 **** typedef enum relopt_kind
RELOPT_KIND_GIST = (1 << 5),
RELOPT_KIND_ATTRIBUTE = (1 << 6),
RELOPT_KIND_TABLESPACE = (1 << 7),
/* if you add a new kind, make sure you update "last_default" too */
! RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_TABLESPACE,
/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
RELOPT_KIND_MAX = (1 << 30)
} relopt_kind;
--- 42,50 ----
RELOPT_KIND_GIST = (1 << 5),
RELOPT_KIND_ATTRIBUTE = (1 << 6),
RELOPT_KIND_TABLESPACE = (1 << 7),
+ RELOPT_KIND_VIEW = (1 << 8),
/* if you add a new kind, make sure you update "last_default" too */
! RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_VIEW,
/* some compilers treat enums as signed ints, so we can't use 1 << 31 */
RELOPT_KIND_MAX = (1 << 30)
} relopt_kind;
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 684,689 **** typedef struct RangeTblEntry
--- 684,691 ----
*/
Query *subquery; /* the sub-query */
+ bool security_view; /* Is the sub-query come from security view? */
+
/*
* Fields valid for a join RTE (else NULL/zero):
*
***************
*** 2202,2207 **** typedef struct ViewStmt
--- 2204,2210 ----
List *aliases; /* target column names */
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
+ bool security; /* the view for row-level security? */
} ViewStmt;
/* ----------------------
*** a/src/include/nodes/primnodes.h
--- b/src/include/nodes/primnodes.h
***************
*** 325,330 **** typedef struct FuncExpr
--- 325,331 ----
CoercionForm funcformat; /* how to display this function call */
List *args; /* arguments to the function */
int location; /* token location, or -1 if unknown */
+ int depth; /* depth of clause in the original query */
} FuncExpr;
/*
***************
*** 368,373 **** typedef struct OpExpr
--- 369,375 ----
bool opretset; /* true if operator returns set */
List *args; /* arguments to the operator (1 or 2) */
int location; /* token location, or -1 if unknown */
+ int depth; /* depth of clause in the original query */
} OpExpr;
/*
***************
*** 400,405 **** typedef struct ScalarArrayOpExpr
--- 402,408 ----
bool useOr; /* true for ANY, false for ALL */
List *args; /* the scalar and array operands */
int location; /* token location, or -1 if unknown */
+ int depth; /* depth of clause in the original query */
} ScalarArrayOpExpr;
/*
***************
*** 659,664 **** typedef struct CoerceViaIO
--- 662,668 ----
/* output typmod is not stored, but is presumed -1 */
CoercionForm coerceformat; /* how to display this node */
int location; /* token location, or -1 if unknown */
+ int depth; /* depth of clause in the original query */
} CoerceViaIO;
/* ----------------
***************
*** 683,688 **** typedef struct ArrayCoerceExpr
--- 687,693 ----
bool isExplicit; /* conversion semantics flag to pass to func */
CoercionForm coerceformat; /* how to display this node */
int location; /* token location, or -1 if unknown */
+ int depth; /* depth of clause in the original query */
} ArrayCoerceExpr;
/* ----------------
***************
*** 852,857 **** typedef struct RowCompareExpr
--- 857,863 ----
List *opfamilies; /* OID list of containing operator families */
List *largs; /* the left-hand input arguments */
List *rargs; /* the right-hand input arguments */
+ int depth; /* depth of clause in the original query */
} RowCompareExpr;
/*
***************
*** 1208,1213 **** typedef struct FromExpr
--- 1214,1220 ----
NodeTag type;
List *fromlist; /* List of join subtrees */
Node *quals; /* qualifiers on join, if any */
+ bool security_view; /* It came from security views */
} FromExpr;
#endif /* PRIMNODES_H */
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 44,49 **** typedef struct QualCost
--- 44,50 ----
{
Cost startup; /* one-time cost */
Cost per_tuple; /* per-evaluation cost */
+ int depth; /* depth of qual in the original query */
} QualCost;
*** a/src/include/optimizer/clauses.h
--- b/src/include/optimizer/clauses.h
***************
*** 67,72 **** extern bool contain_subplans(Node *clause);
--- 67,73 ----
extern bool contain_mutable_functions(Node *clause);
extern bool contain_volatile_functions(Node *clause);
extern bool contain_nonstrict_functions(Node *clause);
+ extern bool contain_leakable_functions(Node *clause);
extern Relids find_nonnullable_rels(Node *clause);
extern List *find_nonnullable_vars(Node *clause);
extern List *find_forced_null_vars(Node *clause);
*** a/src/include/utils/lsyscache.h
--- b/src/include/utils/lsyscache.h
***************
*** 77,82 **** extern RegProcedure get_oprrest(Oid opno);
--- 77,83 ----
extern RegProcedure get_oprjoin(Oid opno);
extern char *get_func_name(Oid funcid);
extern Oid get_func_namespace(Oid funcid);
+ extern Oid get_func_lang(Oid funcid);
extern Oid get_func_rettype(Oid funcid);
extern int get_func_nargs(Oid funcid);
extern Oid get_func_signature(Oid funcid, Oid **argtypes, int *nargs);
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 275,280 **** typedef struct StdRdOptions
--- 275,298 ----
(BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100)
/*
+ * StdViewOptions
+ * Standard contents of rd_options for views
+ */
+ typedef struct StdViewOptions
+ {
+ int32 vl_len_; /* varlena header (do not touch directly!) */
+ bool security_view; /* view has SECURITY VIEW attribute? */
+ } StdViewOptions;
+
+ /*
+ * ViewGetSecurityFlag
+ * Returns the view's security flag
+ */
+ #define ViewGetSecurityFlag(relation) \
+ ((relation)->rd_options ? \
+ ((StdViewOptions *) (relation)->rd_options)->security_view : false)
+
+ /*
* RelationIsValid
* True iff relation descriptor is valid.
*/
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
***************
*** 1319,1325 **** SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
pg_user | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow;
pg_user_mappings | SELECT u.oid AS umid, s.oid AS srvid, s.srvname, u.umuser, CASE WHEN (u.umuser = (0)::oid) THEN 'public'::name ELSE a.rolname END AS usename, CASE WHEN (pg_has_role(s.srvowner, 'USAGE'::text) OR has_server_privilege(s.oid, 'USAGE'::text)) THEN u.umoptions ELSE NULL::text[] END AS umoptions FROM ((pg_user_mapping u LEFT JOIN pg_authid a ON ((a.oid = u.umuser))) JOIN pg_foreign_server s ON ((u.umserver = s.oid)));
! pg_views | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char");
rtest_v1 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;
rtest_vcomp | SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);
rtest_vview1 | SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(*) AS count FROM rtest_view2 y WHERE (y.a = x.a)));
--- 1319,1325 ----
pg_timezone_names | SELECT pg_timezone_names.name, pg_timezone_names.abbrev, pg_timezone_names.utc_offset, pg_timezone_names.is_dst FROM pg_timezone_names() pg_timezone_names(name, abbrev, utc_offset, is_dst);
pg_user | SELECT pg_shadow.usename, pg_shadow.usesysid, pg_shadow.usecreatedb, pg_shadow.usesuper, pg_shadow.usecatupd, '********'::text AS passwd, pg_shadow.valuntil, pg_shadow.useconfig FROM pg_shadow;
pg_user_mappings | SELECT u.oid AS umid, s.oid AS srvid, s.srvname, u.umuser, CASE WHEN (u.umuser = (0)::oid) THEN 'public'::name ELSE a.rolname END AS usename, CASE WHEN (pg_has_role(s.srvowner, 'USAGE'::text) OR has_server_privilege(s.oid, 'USAGE'::text)) THEN u.umoptions ELSE NULL::text[] END AS umoptions FROM ((pg_user_mapping u LEFT JOIN pg_authid a ON ((a.oid = u.umuser))) JOIN pg_foreign_server s ON ((u.umserver = s.oid)));
! pg_views | SELECT n.nspname AS schemaname, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, ((c.reloptions IS NOT NULL) AND ('{security_view=1}'::text[] <@ c.reloptions)) AS security, pg_get_viewdef(c.oid) AS definition FROM (pg_class c LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (c.relkind = 'v'::"char");
rtest_v1 | SELECT rtest_t1.a, rtest_t1.b FROM rtest_t1;
rtest_vcomp | SELECT x.part, (x.size * y.factor) AS size_in_cm FROM rtest_comp x, rtest_unitfact y WHERE (x.unit = y.unit);
rtest_vview1 | SELECT x.a, x.b FROM rtest_view1 x WHERE (0 < (SELECT count(*) AS count FROM rtest_view2 y WHERE (y.a = x.a)));
*** /dev/null
--- b/src/test/regress/expected/security_views.out
***************
*** 0 ****
--- 1,133 ----
+ --
+ -- Clean up in case a prior regression run failed
+ --
+ SET client_min_messages TO 'warning';
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ DROP VIEW IF EXISTS v1;
+ DROP VIEW IF EXISTS v2;
+ DROP VIEW IF EXISTS v3;
+ DROP FUNCTION IF EXISTS f_policy(int);
+ DROP FUNCTION IF EXISTS f_malicious(text);
+ RESET client_min_messages;
+ --
+ -- Test for SECURITY VIEWs
+ --
+ CREATE OR REPLACE FUNCTION f_policy(int)
+ RETURNS bool LANGUAGE 'plpgsql'
+ AS 'BEGIN RETURN $1 % 2 = 0; END';
+ CREATE OR REPLACE FUNCTION f_malicious(text)
+ RETURNS bool COST 0.0001 LANGUAGE 'plpgsql'
+ AS 'BEGIN RAISE NOTICE ''leak: %'', $1; RETURN true; END';
+ CREATE TABLE t1 (a int primary key, b text);
+ NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
+ COPY t1 FROM stdin;
+ CREATE TABLE t2 (x int primary key, y text);
+ NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "t2_pkey" for table "t2"
+ COPY t2 FROM stdin;
+ CREATE OR REPLACE SECURITY VIEW v1 AS
+ SELECT * FROM t1 WHERE f_policy(a);
+ CREATE OR REPLACE VIEW v2 AS
+ SELECT * FROM t1 JOIN t2 ON t1.a = t2.x WHERE f_policy(t1.a);
+ CREATE OR REPLACE SECURITY VIEW v3 AS
+ SELECT * FROM t1 JOIN t2 ON t1.a = t2.x WHERE f_policy(t1.a);
+ SELECT viewname, security FROM pg_views WHERE viewname in ('v1', 'v2', 'v3');
+ viewname | security
+ ----------+----------
+ v1 | t
+ v2 | f
+ v3 | t
+ (3 rows)
+
+ -- f_malicious() is earlier than f_policy()
+ SELECT * FROM t1 WHERE f_policy(a) AND f_malicious(b);
+ NOTICE: leak: aaa
+ NOTICE: leak: bbb
+ NOTICE: leak: ccc
+ NOTICE: leak: ddd
+ NOTICE: leak: eee
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ (2 rows)
+
+ EXPLAIN SELECT * FROM t1 WHERE f_policy(a) AND f_malicious(b);
+ QUERY PLAN
+ -------------------------------------------------------
+ Seq Scan on t1 (cost=0.00..329.80 rows=137 width=36)
+ Filter: (f_malicious(b) AND f_policy(a))
+ (2 rows)
+
+ -- f_policy() is earlier than f_malicious()
+ SELECT * FROM v1 WHERE f_malicious(b);
+ NOTICE: leak: bbb
+ NOTICE: leak: ddd
+ a | b
+ ---+-----
+ 2 | bbb
+ 4 | ddd
+ (2 rows)
+
+ EXPLAIN SELECT * FROM v1 WHERE f_malicious(b);
+ QUERY PLAN
+ -------------------------------------------------------
+ Seq Scan on t1 (cost=0.00..329.80 rows=137 width=36)
+ Filter: (f_policy(a) AND f_malicious(b))
+ (2 rows)
+
+ -- f_malicious() can be pushed down into inside of the join loop
+ SELECT * FROM v2 WHERE f_malicious(y);
+ NOTICE: leak: vvv
+ NOTICE: leak: www
+ NOTICE: leak: xxx
+ NOTICE: leak: yyy
+ NOTICE: leak: zzz
+ a | b | x | y
+ ---+-----+---+-----
+ 2 | bbb | 2 | www
+ 4 | ddd | 4 | yyy
+ (2 rows)
+
+ EXPLAIN SELECT * FROM v2 WHERE f_malicious(y);
+ QUERY PLAN
+ -------------------------------------------------------------------------
+ Nested Loop (cost=0.00..287.63 rows=410 width=72)
+ -> Seq Scan on t2 (cost=0.00..22.30 rows=410 width=36)
+ Filter: f_malicious(y)
+ -> Index Scan using t1_pkey on t1 (cost=0.00..0.63 rows=1 width=36)
+ Index Cond: (a = t2.x)
+ Filter: f_policy(a)
+ (6 rows)
+
+ -- f_malicious() is executed outside of the join loop
+ SELECT * FROM v3 WHERE f_malicious(y);
+ NOTICE: leak: www
+ NOTICE: leak: yyy
+ a | b | x | y
+ ---+-----+---+-----
+ 2 | bbb | 2 | www
+ 4 | ddd | 4 | yyy
+ (2 rows)
+
+ EXPLAIN SELECT * FROM v3 WHERE f_malicious(y);
+ QUERY PLAN
+ -------------------------------------------------------------------
+ Hash Join (cost=37.68..384.39 rows=137 width=72)
+ Hash Cond: (t1.a = t2.x)
+ Join Filter: (f_policy(t1.a) AND f_malicious(t2.y))
+ -> Seq Scan on t1 (cost=0.00..22.30 rows=1230 width=36)
+ -> Hash (cost=22.30..22.30 rows=1230 width=36)
+ -> Seq Scan on t2 (cost=0.00..22.30 rows=1230 width=36)
+ (6 rows)
+
+ --
+ -- Cleanups
+ --
+ DROP VIEW v1;
+ DROP VIEW v2;
+ DROP VIEW v3;
+ DROP TABLE t1;
+ DROP TABLE t2;
+ DROP FUNCTION f_policy(int);
+ DROP FUNCTION f_malicious(text);
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 84,90 **** test: rules
# ----------
# Another group of parallel tests
# ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps
# ----------
# Another group of parallel tests
--- 84,90 ----
# ----------
# Another group of parallel tests
# ----------
! test: select_views security_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps
# ----------
# Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 92,97 **** test: security_label
--- 92,98 ----
test: misc
test: rules
test: select_views
+ test: security_views
test: portals_p2
test: foreign_key
test: cluster
*** /dev/null
--- b/src/test/regress/sql/security_views.sql
***************
*** 0 ****
--- 1,80 ----
+ --
+ -- Clean up in case a prior regression run failed
+ --
+ SET client_min_messages TO 'warning';
+
+ DROP TABLE IF EXISTS t1;
+ DROP TABLE IF EXISTS t2;
+ DROP VIEW IF EXISTS v1;
+ DROP VIEW IF EXISTS v2;
+ DROP VIEW IF EXISTS v3;
+ DROP FUNCTION IF EXISTS f_policy(int);
+ DROP FUNCTION IF EXISTS f_malicious(text);
+
+ RESET client_min_messages;
+
+ --
+ -- Test for SECURITY VIEWs
+ --
+ CREATE OR REPLACE FUNCTION f_policy(int)
+ RETURNS bool LANGUAGE 'plpgsql'
+ AS 'BEGIN RETURN $1 % 2 = 0; END';
+
+ CREATE OR REPLACE FUNCTION f_malicious(text)
+ RETURNS bool COST 0.0001 LANGUAGE 'plpgsql'
+ AS 'BEGIN RAISE NOTICE ''leak: %'', $1; RETURN true; END';
+
+ CREATE TABLE t1 (a int primary key, b text);
+ COPY t1 FROM stdin;
+ 1 aaa
+ 2 bbb
+ 3 ccc
+ 4 ddd
+ 5 eee
+ \.
+
+ CREATE TABLE t2 (x int primary key, y text);
+ COPY t2 FROM stdin;
+ 1 vvv
+ 2 www
+ 3 xxx
+ 4 yyy
+ 5 zzz
+ \.
+
+ CREATE OR REPLACE SECURITY VIEW v1 AS
+ SELECT * FROM t1 WHERE f_policy(a);
+ CREATE OR REPLACE VIEW v2 AS
+ SELECT * FROM t1 JOIN t2 ON t1.a = t2.x WHERE f_policy(t1.a);
+ CREATE OR REPLACE SECURITY VIEW v3 AS
+ SELECT * FROM t1 JOIN t2 ON t1.a = t2.x WHERE f_policy(t1.a);
+
+ SELECT viewname, security FROM pg_views WHERE viewname in ('v1', 'v2', 'v3');
+
+ -- f_malicious() is earlier than f_policy()
+ SELECT * FROM t1 WHERE f_policy(a) AND f_malicious(b);
+ EXPLAIN SELECT * FROM t1 WHERE f_policy(a) AND f_malicious(b);
+
+ -- f_policy() is earlier than f_malicious()
+ SELECT * FROM v1 WHERE f_malicious(b);
+ EXPLAIN SELECT * FROM v1 WHERE f_malicious(b);
+
+ -- f_malicious() can be pushed down into inside of the join loop
+ SELECT * FROM v2 WHERE f_malicious(y);
+ EXPLAIN SELECT * FROM v2 WHERE f_malicious(y);
+
+ -- f_malicious() is executed outside of the join loop
+ SELECT * FROM v3 WHERE f_malicious(y);
+ EXPLAIN SELECT * FROM v3 WHERE f_malicious(y);
+
+ --
+ -- Cleanups
+ --
+ DROP VIEW v1;
+ DROP VIEW v2;
+ DROP VIEW v3;
+ DROP TABLE t1;
+ DROP TABLE t2;
+
+ DROP FUNCTION f_policy(int);
+ DROP FUNCTION f_malicious(text);