mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Make security barrier views automatically updatable
Views which are marked as security_barrier must have their quals applied before any user-defined quals are called, to prevent user-defined functions from being able to see rows which the security barrier view is intended to prevent them from seeing. Remove the restriction on security barrier views being automatically updatable by adding a new securityQuals list to the RTE structure which keeps track of the quals from security barrier views at each level, independently of the user-supplied quals. When RTEs are later discovered which have securityQuals populated, they are turned into subquery RTEs which are marked as security_barrier to prevent any user-supplied quals being pushed down (modulo LEAKPROOF quals). Dean Rasheed, reviewed by Craig Ringer, Simon Riggs, KaiGai Kohei
This commit is contained in:
@ -915,6 +915,12 @@ inheritance_planner(PlannerInfo *root)
|
||||
/* Generate plan */
|
||||
subplan = grouping_planner(&subroot, 0.0 /* retrieve all tuples */ );
|
||||
|
||||
/*
|
||||
* Planning may have modified the query result relation (if there
|
||||
* were security barrier quals on the result RTE).
|
||||
*/
|
||||
appinfo->child_relid = subroot.parse->resultRelation;
|
||||
|
||||
/*
|
||||
* If this child rel was excluded by constraint exclusion, exclude it
|
||||
* from the result plan.
|
||||
@ -932,9 +938,40 @@ inheritance_planner(PlannerInfo *root)
|
||||
if (final_rtable == NIL)
|
||||
final_rtable = subroot.parse->rtable;
|
||||
else
|
||||
final_rtable = list_concat(final_rtable,
|
||||
{
|
||||
List *tmp_rtable = NIL;
|
||||
ListCell *cell1, *cell2;
|
||||
|
||||
/*
|
||||
* Check to see if any of the original RTEs were turned into
|
||||
* subqueries during planning. Currently, this should only ever
|
||||
* happen due to securityQuals being involved which push a
|
||||
* relation down under a subquery, to ensure that the security
|
||||
* barrier quals are evaluated first.
|
||||
*
|
||||
* When this happens, we want to use the new subqueries in the
|
||||
* final rtable.
|
||||
*/
|
||||
forboth(cell1, final_rtable, cell2, subroot.parse->rtable)
|
||||
{
|
||||
RangeTblEntry *rte1 = (RangeTblEntry *) lfirst(cell1);
|
||||
RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(cell2);
|
||||
|
||||
if (rte1->rtekind == RTE_RELATION &&
|
||||
rte2->rtekind == RTE_SUBQUERY)
|
||||
{
|
||||
/* Should only be when there are securityQuals today */
|
||||
Assert(rte1->securityQuals != NIL);
|
||||
tmp_rtable = lappend(tmp_rtable, rte2);
|
||||
}
|
||||
else
|
||||
tmp_rtable = lappend(tmp_rtable, rte1);
|
||||
}
|
||||
|
||||
final_rtable = list_concat(tmp_rtable,
|
||||
list_copy_tail(subroot.parse->rtable,
|
||||
list_length(final_rtable)));
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to collect all the RelOptInfos from all child plans into
|
||||
@ -1162,6 +1199,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
|
||||
/* Preprocess targetlist */
|
||||
tlist = preprocess_targetlist(root, tlist);
|
||||
|
||||
/*
|
||||
* Expand any rangetable entries that have security barrier quals.
|
||||
* This may add new security barrier subquery RTEs to the rangetable.
|
||||
*/
|
||||
expand_security_quals(root, tlist);
|
||||
|
||||
/*
|
||||
* Locate any window functions in the tlist. (We don't need to look
|
||||
* anywhere else, since expressions used in ORDER BY will be in there
|
||||
|
@ -12,6 +12,6 @@ subdir = src/backend/optimizer/prep
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global
|
||||
|
||||
OBJS = prepjointree.o prepqual.o preptlist.o prepunion.o
|
||||
OBJS = prepjointree.o prepqual.o prepsecurity.o preptlist.o prepunion.o
|
||||
|
||||
include $(top_srcdir)/src/backend/common.mk
|
||||
|
466
src/backend/optimizer/prep/prepsecurity.c
Normal file
466
src/backend/optimizer/prep/prepsecurity.c
Normal file
@ -0,0 +1,466 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* prepsecurity.c
|
||||
* Routines for preprocessing security barrier quals.
|
||||
*
|
||||
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* src/backend/optimizer/prep/prepsecurity.c
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "catalog/heap.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/prep.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "utils/rel.h"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int rt_index; /* Index of security barrier RTE */
|
||||
int sublevels_up; /* Current nesting depth */
|
||||
Relation rel; /* RTE relation at rt_index */
|
||||
List *targetlist; /* Targetlist for new subquery RTE */
|
||||
List *colnames; /* Column names in subquery RTE */
|
||||
List *vars_processed; /* List of Vars already processed */
|
||||
} security_barrier_replace_vars_context;
|
||||
|
||||
static void expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
|
||||
RangeTblEntry *rte, Node *qual);
|
||||
|
||||
static void security_barrier_replace_vars(Node *node,
|
||||
security_barrier_replace_vars_context *context);
|
||||
|
||||
static bool security_barrier_replace_vars_walker(Node *node,
|
||||
security_barrier_replace_vars_context *context);
|
||||
|
||||
|
||||
/*
|
||||
* expand_security_quals -
|
||||
* expands any security barrier quals on RTEs in the query rtable, turning
|
||||
* them into security barrier subqueries.
|
||||
*
|
||||
* 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-defined security
|
||||
* barriers. The first security barrier qual in the list will be used in the
|
||||
* innermost subquery.
|
||||
*/
|
||||
void
|
||||
expand_security_quals(PlannerInfo *root, List *tlist)
|
||||
{
|
||||
Query *parse = root->parse;
|
||||
int rt_index;
|
||||
ListCell *cell;
|
||||
|
||||
/*
|
||||
* Process each RTE in the rtable list.
|
||||
*
|
||||
* We only ever modify entries in place and append to the rtable, so it is
|
||||
* safe to use a foreach loop here.
|
||||
*/
|
||||
rt_index = 0;
|
||||
foreach(cell, parse->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell);
|
||||
|
||||
rt_index++;
|
||||
|
||||
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 != parse->resultRelation &&
|
||||
!rangeTableEntry_used((Node *) parse, rt_index, 0))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* 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 original RTE will be expanded to become the source of rows to
|
||||
* update/delete.
|
||||
*/
|
||||
if (rt_index == parse->resultRelation)
|
||||
{
|
||||
RangeTblEntry *newrte = copyObject(rte);
|
||||
parse->rtable = lappend(parse->rtable, newrte);
|
||||
parse->resultRelation = list_length(parse->rtable);
|
||||
|
||||
/*
|
||||
* Wipe out any copied security barrier 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 they are, meaning that they pull OLD values from the
|
||||
* expanded RTE. But in the RETURNING list and in any WITH CHECK
|
||||
* OPTION quals, we want such Vars to represent NEW values, so
|
||||
* change them to reference the new RTE.
|
||||
*/
|
||||
ChangeVarNodes((Node *) parse->returningList, rt_index,
|
||||
parse->resultRelation, 0);
|
||||
|
||||
ChangeVarNodes((Node *) parse->withCheckOptions, rt_index,
|
||||
parse->resultRelation, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process each security barrier qual in turn, starting with the
|
||||
* innermost one (the first in the list) and working outwards.
|
||||
*
|
||||
* We remove each qual from the list before processing it, so that its
|
||||
* variables aren't modified by expand_security_qual. Also we don't
|
||||
* necessarily want the attributes referred to by the qual to be
|
||||
* exposed by the newly built subquery.
|
||||
*/
|
||||
while (rte->securityQuals != NIL)
|
||||
{
|
||||
Node *qual = (Node *) linitial(rte->securityQuals);
|
||||
rte->securityQuals = list_delete_first(rte->securityQuals);
|
||||
|
||||
ChangeVarNodes(qual, rt_index, 1, 0);
|
||||
expand_security_qual(root, tlist, rt_index, rte, qual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* expand_security_qual -
|
||||
* expand the specified security barrier qual on a query RTE, turning the
|
||||
* RTE into a security barrier subquery.
|
||||
*/
|
||||
static void
|
||||
expand_security_qual(PlannerInfo *root, List *tlist, int rt_index,
|
||||
RangeTblEntry *rte, Node *qual)
|
||||
{
|
||||
Query *parse = root->parse;
|
||||
Oid relid = rte->relid;
|
||||
Query *subquery;
|
||||
RangeTblEntry *subrte;
|
||||
RangeTblRef *subrtr;
|
||||
PlanRowMark *rc;
|
||||
security_barrier_replace_vars_context context;
|
||||
ListCell *cell;
|
||||
|
||||
/*
|
||||
* There should only be 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:
|
||||
/*
|
||||
* Turn the relation RTE into a security barrier subquery RTE,
|
||||
* moving all permissions checks down into the subquery.
|
||||
*/
|
||||
subquery = makeNode(Query);
|
||||
subquery->commandType = CMD_SELECT;
|
||||
subquery->querySource = QSRC_INSTEAD_RULE;
|
||||
|
||||
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);
|
||||
|
||||
rte->rtekind = RTE_SUBQUERY;
|
||||
rte->relid = InvalidOid;
|
||||
rte->subquery = subquery;
|
||||
rte->security_barrier = true;
|
||||
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;
|
||||
|
||||
/*
|
||||
* Now deal with any PlanRowMark on this RTE by requesting a lock
|
||||
* of the same strength on the RTE copied down to the subquery.
|
||||
*
|
||||
* Note that we can't push the user-defined quals down since they
|
||||
* may included untrusted functions and that means that we will
|
||||
* end up locking all rows which pass the securityQuals, even if
|
||||
* those rows don't pass the user-defined quals. This is currently
|
||||
* documented behavior, but it'd be nice to come up with a better
|
||||
* solution some day.
|
||||
*/
|
||||
rc = get_plan_rowmark(root->rowMarks, rt_index);
|
||||
if (rc != NULL)
|
||||
{
|
||||
switch (rc->markType)
|
||||
{
|
||||
case ROW_MARK_EXCLUSIVE:
|
||||
applyLockingClause(subquery, 1, LCS_FORUPDATE,
|
||||
rc->noWait, false);
|
||||
break;
|
||||
case ROW_MARK_NOKEYEXCLUSIVE:
|
||||
applyLockingClause(subquery, 1, LCS_FORNOKEYUPDATE,
|
||||
rc->noWait, false);
|
||||
break;
|
||||
case ROW_MARK_SHARE:
|
||||
applyLockingClause(subquery, 1, LCS_FORSHARE,
|
||||
rc->noWait, false);
|
||||
break;
|
||||
case ROW_MARK_KEYSHARE:
|
||||
applyLockingClause(subquery, 1, LCS_FORKEYSHARE,
|
||||
rc->noWait, false);
|
||||
break;
|
||||
case ROW_MARK_REFERENCE:
|
||||
case ROW_MARK_COPY:
|
||||
/* No locking needed */
|
||||
break;
|
||||
}
|
||||
root->rowMarks = list_delete(root->rowMarks, rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Replace any variables in the outer query that refer to the
|
||||
* original relation RTE with references to columns that we will
|
||||
* expose in the new subquery, building the subquery's targetlist
|
||||
* as we go.
|
||||
*/
|
||||
context.rt_index = rt_index;
|
||||
context.sublevels_up = 0;
|
||||
context.rel = heap_open(relid, NoLock);
|
||||
context.targetlist = NIL;
|
||||
context.colnames = NIL;
|
||||
context.vars_processed = NIL;
|
||||
|
||||
security_barrier_replace_vars((Node *) parse, &context);
|
||||
security_barrier_replace_vars((Node *) tlist, &context);
|
||||
|
||||
heap_close(context.rel, NoLock);
|
||||
|
||||
/* Now we know what columns the subquery needs to expose */
|
||||
rte->subquery->targetList = context.targetlist;
|
||||
rte->eref = makeAlias(rte->eref->aliasname, context.colnames);
|
||||
|
||||
break;
|
||||
|
||||
case RTE_SUBQUERY:
|
||||
/*
|
||||
* Build a new subquery that includes all the same columns as the
|
||||
* original subquery.
|
||||
*/
|
||||
subquery = makeNode(Query);
|
||||
subquery->commandType = CMD_SELECT;
|
||||
subquery->querySource = QSRC_INSTEAD_RULE;
|
||||
subquery->targetList = NIL;
|
||||
|
||||
foreach(cell, rte->subquery->targetList)
|
||||
{
|
||||
TargetEntry *tle;
|
||||
Var *var;
|
||||
|
||||
tle = (TargetEntry *) lfirst(cell);
|
||||
var = makeVarFromTargetEntry(1, tle);
|
||||
|
||||
tle = makeTargetEntry((Expr *) var,
|
||||
list_length(subquery->targetList) + 1,
|
||||
pstrdup(tle->resname),
|
||||
tle->resjunk);
|
||||
subquery->targetList = lappend(subquery->targetList, tle);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* security_barrier_replace_vars -
|
||||
* Apply security barrier variable replacement to an expression tree.
|
||||
*
|
||||
* This also builds/updates a targetlist with entries for each replacement
|
||||
* variable that needs to be exposed by the security barrier subquery RTE.
|
||||
*
|
||||
* NOTE: although this has the form of a walker, we cheat and modify the
|
||||
* nodes in-place. The given expression tree should have been copied
|
||||
* earlier to ensure that no unwanted side-effects occur!
|
||||
*/
|
||||
static void
|
||||
security_barrier_replace_vars(Node *node,
|
||||
security_barrier_replace_vars_context *context)
|
||||
{
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, go straight to query_tree_walker to make sure that
|
||||
* sublevels_up doesn't get incremented prematurely.
|
||||
*/
|
||||
if (node && IsA(node, Query))
|
||||
query_tree_walker((Query *) node,
|
||||
security_barrier_replace_vars_walker,
|
||||
(void *) context, 0);
|
||||
else
|
||||
security_barrier_replace_vars_walker(node, context);
|
||||
}
|
||||
|
||||
static bool
|
||||
security_barrier_replace_vars_walker(Node *node,
|
||||
security_barrier_replace_vars_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
/*
|
||||
* Note that the same Var may be present in different lists, so we
|
||||
* need to take care not to process it multiple times.
|
||||
*/
|
||||
if (var->varno == context->rt_index &&
|
||||
var->varlevelsup == context->sublevels_up &&
|
||||
!list_member_ptr(context->vars_processed, var))
|
||||
{
|
||||
/*
|
||||
* Found a matching variable. Make sure that it is in the subquery
|
||||
* targetlist and map its attno accordingly.
|
||||
*/
|
||||
AttrNumber attno;
|
||||
ListCell *l;
|
||||
TargetEntry *tle;
|
||||
char *attname;
|
||||
Var *newvar;
|
||||
|
||||
/* Search for the base attribute in the subquery targetlist */
|
||||
attno = InvalidAttrNumber;
|
||||
foreach(l, context->targetlist)
|
||||
{
|
||||
tle = (TargetEntry *) lfirst(l);
|
||||
attno++;
|
||||
|
||||
Assert(IsA(tle->expr, Var));
|
||||
if (((Var *) tle->expr)->varattno == var->varattno &&
|
||||
((Var *) tle->expr)->varcollid == var->varcollid)
|
||||
{
|
||||
/* Map the variable onto this subquery targetlist entry */
|
||||
var->varattno = attno;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Not in the subquery targetlist, so add it. Get its name. */
|
||||
if (var->varattno < 0)
|
||||
{
|
||||
Form_pg_attribute att_tup;
|
||||
|
||||
att_tup = SystemAttributeDefinition(var->varattno,
|
||||
context->rel->rd_rel->relhasoids);
|
||||
attname = NameStr(att_tup->attname);
|
||||
}
|
||||
else if (var->varattno == InvalidAttrNumber)
|
||||
{
|
||||
attname = "wholerow";
|
||||
}
|
||||
else if (var->varattno <= context->rel->rd_att->natts)
|
||||
{
|
||||
Form_pg_attribute att_tup;
|
||||
|
||||
att_tup = context->rel->rd_att->attrs[var->varattno - 1];
|
||||
attname = NameStr(att_tup->attname);
|
||||
}
|
||||
else
|
||||
{
|
||||
elog(ERROR, "invalid attribute number %d in security_barrier_replace_vars", var->varattno);
|
||||
}
|
||||
|
||||
/* New variable for subquery targetlist */
|
||||
newvar = copyObject(var);
|
||||
newvar->varno = 1;
|
||||
|
||||
attno = list_length(context->targetlist) + 1;
|
||||
tle = makeTargetEntry((Expr *) newvar,
|
||||
attno,
|
||||
pstrdup(attname),
|
||||
false);
|
||||
|
||||
context->targetlist = lappend(context->targetlist, tle);
|
||||
|
||||
context->colnames = lappend(context->colnames,
|
||||
makeString(pstrdup(attname)));
|
||||
|
||||
/* Update the outer query's variable */
|
||||
var->varattno = attno;
|
||||
|
||||
/* Remember this Var so that we don't process it again */
|
||||
context->vars_processed = lappend(context->vars_processed, var);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/* Recurse into subselects */
|
||||
bool result;
|
||||
|
||||
context->sublevels_up++;
|
||||
result = query_tree_walker((Query *) node,
|
||||
security_barrier_replace_vars_walker,
|
||||
(void *) context, 0);
|
||||
context->sublevels_up--;
|
||||
return result;
|
||||
}
|
||||
|
||||
return expression_tree_walker(node, security_barrier_replace_vars_walker,
|
||||
(void *) context);
|
||||
}
|
@ -55,6 +55,7 @@ typedef struct
|
||||
{
|
||||
PlannerInfo *root;
|
||||
AppendRelInfo *appinfo;
|
||||
int sublevels_up;
|
||||
} adjust_appendrel_attrs_context;
|
||||
|
||||
static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
|
||||
@ -1580,8 +1581,9 @@ translate_col_privs(const Bitmapset *parent_privs,
|
||||
* child rel instead. We also update rtindexes appearing outside Vars,
|
||||
* such as resultRelation and jointree relids.
|
||||
*
|
||||
* Note: this is only applied after conversion of sublinks to subplans,
|
||||
* so we don't need to cope with recursion into sub-queries.
|
||||
* Note: this is applied after conversion of sublinks to subplans in the
|
||||
* query jointree, but there may still be sublinks in the security barrier
|
||||
* quals of RTEs, so we do need to cope with recursion into sub-queries.
|
||||
*
|
||||
* Note: this is not hugely different from what pullup_replace_vars() does;
|
||||
* maybe we should try to fold the two routines together.
|
||||
@ -1594,9 +1596,12 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, AppendRelInfo *appinfo)
|
||||
|
||||
context.root = root;
|
||||
context.appinfo = appinfo;
|
||||
context.sublevels_up = 0;
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree.
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
* it's a Query, go straight to query_tree_walker to make sure that
|
||||
* sublevels_up doesn't get incremented prematurely.
|
||||
*/
|
||||
if (node && IsA(node, Query))
|
||||
{
|
||||
@ -1635,7 +1640,7 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
{
|
||||
Var *var = (Var *) copyObject(node);
|
||||
|
||||
if (var->varlevelsup == 0 &&
|
||||
if (var->varlevelsup == context->sublevels_up &&
|
||||
var->varno == appinfo->parent_relid)
|
||||
{
|
||||
var->varno = appinfo->child_relid;
|
||||
@ -1652,6 +1657,7 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
if (newnode == NULL)
|
||||
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
|
||||
var->varattno, get_rel_name(appinfo->parent_reloid));
|
||||
((Var *) newnode)->varlevelsup += context->sublevels_up;
|
||||
return newnode;
|
||||
}
|
||||
else if (var->varattno == 0)
|
||||
@ -1694,10 +1700,16 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
RowExpr *rowexpr;
|
||||
List *fields;
|
||||
RangeTblEntry *rte;
|
||||
ListCell *lc;
|
||||
|
||||
rte = rt_fetch(appinfo->parent_relid,
|
||||
context->root->parse->rtable);
|
||||
fields = (List *) copyObject(appinfo->translated_vars);
|
||||
foreach(lc, fields)
|
||||
{
|
||||
Var *field = (Var *) lfirst(lc);
|
||||
field->varlevelsup += context->sublevels_up;
|
||||
}
|
||||
rowexpr = makeNode(RowExpr);
|
||||
rowexpr->args = fields;
|
||||
rowexpr->row_typeid = var->vartype;
|
||||
@ -1716,7 +1728,8 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
{
|
||||
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
|
||||
|
||||
if (cexpr->cvarno == appinfo->parent_relid)
|
||||
if (context->sublevels_up == 0 &&
|
||||
cexpr->cvarno == appinfo->parent_relid)
|
||||
cexpr->cvarno = appinfo->child_relid;
|
||||
return (Node *) cexpr;
|
||||
}
|
||||
@ -1724,7 +1737,8 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
{
|
||||
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
|
||||
|
||||
if (rtr->rtindex == appinfo->parent_relid)
|
||||
if (context->sublevels_up == 0 &&
|
||||
rtr->rtindex == appinfo->parent_relid)
|
||||
rtr->rtindex = appinfo->child_relid;
|
||||
return (Node *) rtr;
|
||||
}
|
||||
@ -1737,7 +1751,8 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
adjust_appendrel_attrs_mutator,
|
||||
(void *) context);
|
||||
/* now fix JoinExpr's rtindex (probably never happens) */
|
||||
if (j->rtindex == appinfo->parent_relid)
|
||||
if (context->sublevels_up == 0 &&
|
||||
j->rtindex == appinfo->parent_relid)
|
||||
j->rtindex = appinfo->child_relid;
|
||||
return (Node *) j;
|
||||
}
|
||||
@ -1750,7 +1765,7 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
adjust_appendrel_attrs_mutator,
|
||||
(void *) context);
|
||||
/* now fix PlaceHolderVar's relid sets */
|
||||
if (phv->phlevelsup == 0)
|
||||
if (phv->phlevelsup == context->sublevels_up)
|
||||
phv->phrels = adjust_relid_set(phv->phrels,
|
||||
appinfo->parent_relid,
|
||||
appinfo->child_relid);
|
||||
@ -1822,12 +1837,29 @@ adjust_appendrel_attrs_mutator(Node *node,
|
||||
return (Node *) newinfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: we do not need to recurse into sublinks, because they should
|
||||
* already have been converted to subplans before we see them.
|
||||
*/
|
||||
Assert(!IsA(node, SubLink));
|
||||
Assert(!IsA(node, Query));
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
/*
|
||||
* Recurse into sublink subqueries. This should only be possible in
|
||||
* security barrier quals of top-level RTEs. All other sublinks should
|
||||
* have already been converted to subplans during expression
|
||||
* preprocessing, but this doesn't happen for security barrier quals,
|
||||
* since they are destined to become quals of a subquery RTE, which
|
||||
* will be recursively planned, and so should not be preprocessed at
|
||||
* this stage.
|
||||
*
|
||||
* We don't explicitly Assert() for securityQuals here simply because
|
||||
* it's not trivial to do so.
|
||||
*/
|
||||
Query *newnode;
|
||||
|
||||
context->sublevels_up++;
|
||||
newnode = query_tree_mutator((Query *) node,
|
||||
adjust_appendrel_attrs_mutator,
|
||||
(void *) context, 0);
|
||||
context->sublevels_up--;
|
||||
return (Node *) newnode;
|
||||
}
|
||||
|
||||
return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
|
||||
(void *) context);
|
||||
|
Reference in New Issue
Block a user