mirror of
https://github.com/postgres/postgres.git
synced 2026-04-15 22:10:45 -04:00
Previously, pull_varnos() took the relids of a PlaceHolderVar as being
equal to the relids in its contents, but that fails to account for the
possibility that we have to postpone evaluation of the PHV due to outer
joins. This could result in a malformed plan. The known cases end up
triggering the "failed to assign all NestLoopParams to plan nodes"
sanity check in createplan.c, but other symptoms may be possible.
The right value to use is the join level we actually intend to evaluate
the PHV at. We can get that from the ph_eval_at field of the associated
PlaceHolderInfo. However, there are some places that call pull_varnos()
before the PlaceHolderInfos have been created; in that case, fall back
to the conservative assumption that the PHV will be evaluated at its
syntactic level. (In principle this might result in missing some legal
optimization, but I'm not aware of any cases where it's an issue in
practice.) Things are also a bit ticklish for calls occurring during
deconstruct_jointree(), but AFAICS the ph_eval_at fields should have
reached their final values by the time we need them.
The main problem in making this work is that pull_varnos() has no
way to get at the PlaceHolderInfos. We can fix that easily, if a
bit tediously, in HEAD by passing it the planner "root" pointer.
In the back branches that'd cause an unacceptable API/ABI break for
extensions, so leave the existing entry points alone and add new ones
with the additional parameter. (If an old entry point is called and
encounters a PHV, it'll fall back to using the syntactic level,
again possibly missing some valid optimization.)
Back-patch to v12. The computation is surely also wrong before that,
but it appears that we cannot reach a bad plan thanks to join order
restrictions imposed on the subquery that the PlaceHolderVar came from.
The error only became reachable when commit 4be058fe9 allowed trivial
subqueries to be collapsed out completely, eliminating their join order
restrictions.
Per report from Stephan Springl.
Discussion: https://postgr.es/m/171041.1610849523@sss.pgh.pa.us
744 lines
25 KiB
C
744 lines
25 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* inherit.c
|
|
* Routines to process child relations in inheritance trees
|
|
*
|
|
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/optimizer/path/inherit.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/sysattr.h"
|
|
#include "access/table.h"
|
|
#include "catalog/partition.h"
|
|
#include "catalog/pg_inherits.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "optimizer/appendinfo.h"
|
|
#include "optimizer/inherit.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "optimizer/pathnode.h"
|
|
#include "optimizer/planmain.h"
|
|
#include "optimizer/planner.h"
|
|
#include "optimizer/prep.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
#include "parser/parsetree.h"
|
|
#include "partitioning/partdesc.h"
|
|
#include "partitioning/partprune.h"
|
|
#include "utils/rel.h"
|
|
|
|
|
|
/* source-code-compatibility hacks for pull_varnos() API change */
|
|
#define make_restrictinfo(a,b,c,d,e,f,g,h,i) make_restrictinfo_new(a,b,c,d,e,f,g,h,i)
|
|
|
|
static void expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
|
|
RangeTblEntry *parentrte,
|
|
Index parentRTindex, Relation parentrel,
|
|
PlanRowMark *top_parentrc, LOCKMODE lockmode);
|
|
static void expand_single_inheritance_child(PlannerInfo *root,
|
|
RangeTblEntry *parentrte,
|
|
Index parentRTindex, Relation parentrel,
|
|
PlanRowMark *top_parentrc, Relation childrel,
|
|
RangeTblEntry **childrte_p,
|
|
Index *childRTindex_p);
|
|
static Bitmapset *translate_col_privs(const Bitmapset *parent_privs,
|
|
List *translated_vars);
|
|
static void expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
|
|
RangeTblEntry *rte, Index rti);
|
|
|
|
|
|
/*
|
|
* expand_inherited_rtentry
|
|
* Expand a rangetable entry that has the "inh" bit set.
|
|
*
|
|
* "inh" is only allowed in two cases: RELATION and SUBQUERY RTEs.
|
|
*
|
|
* "inh" on a plain RELATION RTE means that it is a partitioned table or the
|
|
* parent of a traditional-inheritance set. In this case we must add entries
|
|
* for all the interesting child tables to the query's rangetable, and build
|
|
* additional planner data structures for them, including RelOptInfos,
|
|
* AppendRelInfos, and possibly PlanRowMarks.
|
|
*
|
|
* Note that the original RTE is considered to represent the whole inheritance
|
|
* set. In the case of traditional inheritance, the first of the generated
|
|
* RTEs is an RTE for the same table, but with inh = false, to represent the
|
|
* parent table in its role as a simple member of the inheritance set. For
|
|
* partitioning, we don't need a second RTE because the partitioned table
|
|
* itself has no data and need not be scanned.
|
|
*
|
|
* "inh" on a SUBQUERY RTE means that it's the parent of a UNION ALL group,
|
|
* which is treated as an appendrel similarly to inheritance cases; however,
|
|
* we already made RTEs and AppendRelInfos for the subqueries. We only need
|
|
* to build RelOptInfos for them, which is done by expand_appendrel_subquery.
|
|
*/
|
|
void
|
|
expand_inherited_rtentry(PlannerInfo *root, RelOptInfo *rel,
|
|
RangeTblEntry *rte, Index rti)
|
|
{
|
|
Oid parentOID;
|
|
Relation oldrelation;
|
|
LOCKMODE lockmode;
|
|
PlanRowMark *oldrc;
|
|
bool old_isParent = false;
|
|
int old_allMarkTypes = 0;
|
|
|
|
Assert(rte->inh); /* else caller error */
|
|
|
|
if (rte->rtekind == RTE_SUBQUERY)
|
|
{
|
|
expand_appendrel_subquery(root, rel, rte, rti);
|
|
return;
|
|
}
|
|
|
|
Assert(rte->rtekind == RTE_RELATION);
|
|
|
|
parentOID = rte->relid;
|
|
|
|
/*
|
|
* We used to check has_subclass() here, but there's no longer any need
|
|
* to, because subquery_planner already did.
|
|
*/
|
|
|
|
/*
|
|
* The rewriter should already have obtained an appropriate lock on each
|
|
* relation named in the query, so we can open the parent relation without
|
|
* locking it. However, for each child relation we add to the query, we
|
|
* must obtain an appropriate lock, because this will be the first use of
|
|
* those relations in the parse/rewrite/plan pipeline. Child rels should
|
|
* use the same lockmode as their parent.
|
|
*/
|
|
oldrelation = table_open(parentOID, NoLock);
|
|
lockmode = rte->rellockmode;
|
|
|
|
/*
|
|
* If parent relation is selected FOR UPDATE/SHARE, we need to mark its
|
|
* PlanRowMark as isParent = true, and generate a new PlanRowMark for each
|
|
* child.
|
|
*/
|
|
oldrc = get_plan_rowmark(root->rowMarks, rti);
|
|
if (oldrc)
|
|
{
|
|
old_isParent = oldrc->isParent;
|
|
oldrc->isParent = true;
|
|
/* Save initial value of allMarkTypes before children add to it */
|
|
old_allMarkTypes = oldrc->allMarkTypes;
|
|
}
|
|
|
|
/* Scan the inheritance set and expand it */
|
|
if (oldrelation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
|
{
|
|
/*
|
|
* Partitioned table, so set up for partitioning.
|
|
*/
|
|
Assert(rte->relkind == RELKIND_PARTITIONED_TABLE);
|
|
|
|
/*
|
|
* Recursively expand and lock the partitions. While at it, also
|
|
* extract the partition key columns of all the partitioned tables.
|
|
*/
|
|
expand_partitioned_rtentry(root, rel, rte, rti,
|
|
oldrelation, oldrc, lockmode);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Ordinary table, so process traditional-inheritance children. (Note
|
|
* that partitioned tables are not allowed to have inheritance
|
|
* children, so it's not possible for both cases to apply.)
|
|
*/
|
|
List *inhOIDs;
|
|
ListCell *l;
|
|
|
|
/* Scan for all members of inheritance set, acquire needed locks */
|
|
inhOIDs = find_all_inheritors(parentOID, lockmode, NULL);
|
|
|
|
/*
|
|
* We used to special-case the situation where the table no longer has
|
|
* any children, by clearing rte->inh and exiting. That no longer
|
|
* works, because this function doesn't get run until after decisions
|
|
* have been made that depend on rte->inh. We have to treat such
|
|
* situations as normal inheritance. The table itself should always
|
|
* have been found, though.
|
|
*/
|
|
Assert(inhOIDs != NIL);
|
|
Assert(linitial_oid(inhOIDs) == parentOID);
|
|
|
|
/* Expand simple_rel_array and friends to hold child objects. */
|
|
expand_planner_arrays(root, list_length(inhOIDs));
|
|
|
|
/*
|
|
* Expand inheritance children in the order the OIDs were returned by
|
|
* find_all_inheritors.
|
|
*/
|
|
foreach(l, inhOIDs)
|
|
{
|
|
Oid childOID = lfirst_oid(l);
|
|
Relation newrelation;
|
|
RangeTblEntry *childrte;
|
|
Index childRTindex;
|
|
|
|
/* Open rel if needed; we already have required locks */
|
|
if (childOID != parentOID)
|
|
newrelation = table_open(childOID, NoLock);
|
|
else
|
|
newrelation = oldrelation;
|
|
|
|
/*
|
|
* It is possible that the parent table has children that are temp
|
|
* tables of other backends. We cannot safely access such tables
|
|
* (because of buffering issues), and the best thing to do seems
|
|
* to be to silently ignore them.
|
|
*/
|
|
if (childOID != parentOID && RELATION_IS_OTHER_TEMP(newrelation))
|
|
{
|
|
table_close(newrelation, lockmode);
|
|
continue;
|
|
}
|
|
|
|
/* Create RTE and AppendRelInfo, plus PlanRowMark if needed. */
|
|
expand_single_inheritance_child(root, rte, rti, oldrelation,
|
|
oldrc, newrelation,
|
|
&childrte, &childRTindex);
|
|
|
|
/* Create the otherrel RelOptInfo too. */
|
|
(void) build_simple_rel(root, childRTindex, rel);
|
|
|
|
/* Close child relations, but keep locks */
|
|
if (childOID != parentOID)
|
|
table_close(newrelation, NoLock);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Some children might require different mark types, which would've been
|
|
* reported into oldrc. If so, add relevant entries to the top-level
|
|
* targetlist and update parent rel's reltarget. This should match what
|
|
* preprocess_targetlist() would have added if the mark types had been
|
|
* requested originally.
|
|
*/
|
|
if (oldrc)
|
|
{
|
|
int new_allMarkTypes = oldrc->allMarkTypes;
|
|
Var *var;
|
|
TargetEntry *tle;
|
|
char resname[32];
|
|
List *newvars = NIL;
|
|
|
|
/* The old PlanRowMark should already have necessitated adding TID */
|
|
Assert(old_allMarkTypes & ~(1 << ROW_MARK_COPY));
|
|
|
|
/* Add whole-row junk Var if needed, unless we had it already */
|
|
if ((new_allMarkTypes & (1 << ROW_MARK_COPY)) &&
|
|
!(old_allMarkTypes & (1 << ROW_MARK_COPY)))
|
|
{
|
|
var = makeWholeRowVar(planner_rt_fetch(oldrc->rti, root),
|
|
oldrc->rti,
|
|
0,
|
|
false);
|
|
snprintf(resname, sizeof(resname), "wholerow%u", oldrc->rowmarkId);
|
|
tle = makeTargetEntry((Expr *) var,
|
|
list_length(root->processed_tlist) + 1,
|
|
pstrdup(resname),
|
|
true);
|
|
root->processed_tlist = lappend(root->processed_tlist, tle);
|
|
newvars = lappend(newvars, var);
|
|
}
|
|
|
|
/* Add tableoid junk Var, unless we had it already */
|
|
if (!old_isParent)
|
|
{
|
|
var = makeVar(oldrc->rti,
|
|
TableOidAttributeNumber,
|
|
OIDOID,
|
|
-1,
|
|
InvalidOid,
|
|
0);
|
|
snprintf(resname, sizeof(resname), "tableoid%u", oldrc->rowmarkId);
|
|
tle = makeTargetEntry((Expr *) var,
|
|
list_length(root->processed_tlist) + 1,
|
|
pstrdup(resname),
|
|
true);
|
|
root->processed_tlist = lappend(root->processed_tlist, tle);
|
|
newvars = lappend(newvars, var);
|
|
}
|
|
|
|
/*
|
|
* Add the newly added Vars to parent's reltarget. We needn't worry
|
|
* about the children's reltargets, they'll be made later.
|
|
*/
|
|
add_vars_to_targetlist(root, newvars, bms_make_singleton(0), false);
|
|
}
|
|
|
|
table_close(oldrelation, NoLock);
|
|
}
|
|
|
|
/*
|
|
* expand_partitioned_rtentry
|
|
* Recursively expand an RTE for a partitioned table.
|
|
*/
|
|
static void
|
|
expand_partitioned_rtentry(PlannerInfo *root, RelOptInfo *relinfo,
|
|
RangeTblEntry *parentrte,
|
|
Index parentRTindex, Relation parentrel,
|
|
PlanRowMark *top_parentrc, LOCKMODE lockmode)
|
|
{
|
|
PartitionDesc partdesc;
|
|
Bitmapset *live_parts;
|
|
int num_live_parts;
|
|
int i;
|
|
|
|
check_stack_depth();
|
|
|
|
Assert(parentrte->inh);
|
|
|
|
partdesc = PartitionDirectoryLookup(root->glob->partition_directory,
|
|
parentrel);
|
|
|
|
/* A partitioned table should always have a partition descriptor. */
|
|
Assert(partdesc);
|
|
|
|
/*
|
|
* Note down whether any partition key cols are being updated. Though it's
|
|
* the root partitioned table's updatedCols we are interested in, we
|
|
* instead use parentrte to get the updatedCols. This is convenient
|
|
* because parentrte already has the root partrel's updatedCols translated
|
|
* to match the attribute ordering of parentrel.
|
|
*/
|
|
if (!root->partColsUpdated)
|
|
root->partColsUpdated =
|
|
has_partition_attrs(parentrel, parentrte->updatedCols, NULL);
|
|
|
|
/*
|
|
* There shouldn't be any generated columns in the partition key.
|
|
*/
|
|
Assert(!has_partition_attrs(parentrel, parentrte->extraUpdatedCols, NULL));
|
|
|
|
/* Nothing further to do here if there are no partitions. */
|
|
if (partdesc->nparts == 0)
|
|
return;
|
|
|
|
/*
|
|
* Perform partition pruning using restriction clauses assigned to parent
|
|
* relation. live_parts will contain PartitionDesc indexes of partitions
|
|
* that survive pruning. Below, we will initialize child objects for the
|
|
* surviving partitions.
|
|
*/
|
|
live_parts = prune_append_rel_partitions(relinfo);
|
|
|
|
/* Expand simple_rel_array and friends to hold child objects. */
|
|
num_live_parts = bms_num_members(live_parts);
|
|
if (num_live_parts > 0)
|
|
expand_planner_arrays(root, num_live_parts);
|
|
|
|
/*
|
|
* We also store partition RelOptInfo pointers in the parent relation.
|
|
* Since we're palloc0'ing, slots corresponding to pruned partitions will
|
|
* contain NULL.
|
|
*/
|
|
Assert(relinfo->part_rels == NULL);
|
|
relinfo->part_rels = (RelOptInfo **)
|
|
palloc0(relinfo->nparts * sizeof(RelOptInfo *));
|
|
|
|
/*
|
|
* Create a child RTE for each live partition. Note that unlike
|
|
* traditional inheritance, we don't need a child RTE for the partitioned
|
|
* table itself, because it's not going to be scanned.
|
|
*/
|
|
i = -1;
|
|
while ((i = bms_next_member(live_parts, i)) >= 0)
|
|
{
|
|
Oid childOID = partdesc->oids[i];
|
|
Relation childrel;
|
|
RangeTblEntry *childrte;
|
|
Index childRTindex;
|
|
RelOptInfo *childrelinfo;
|
|
|
|
/* Open rel, acquiring required locks */
|
|
childrel = table_open(childOID, lockmode);
|
|
|
|
/*
|
|
* Temporary partitions belonging to other sessions should have been
|
|
* disallowed at definition, but for paranoia's sake, let's double
|
|
* check.
|
|
*/
|
|
if (RELATION_IS_OTHER_TEMP(childrel))
|
|
elog(ERROR, "temporary relation from another session found as partition");
|
|
|
|
/* Create RTE and AppendRelInfo, plus PlanRowMark if needed. */
|
|
expand_single_inheritance_child(root, parentrte, parentRTindex,
|
|
parentrel, top_parentrc, childrel,
|
|
&childrte, &childRTindex);
|
|
|
|
/* Create the otherrel RelOptInfo too. */
|
|
childrelinfo = build_simple_rel(root, childRTindex, relinfo);
|
|
relinfo->part_rels[i] = childrelinfo;
|
|
|
|
/* If this child is itself partitioned, recurse */
|
|
if (childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
|
|
expand_partitioned_rtentry(root, childrelinfo,
|
|
childrte, childRTindex,
|
|
childrel, top_parentrc, lockmode);
|
|
|
|
/* Close child relation, but keep locks */
|
|
table_close(childrel, NoLock);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* expand_single_inheritance_child
|
|
* Build a RangeTblEntry and an AppendRelInfo, plus maybe a PlanRowMark.
|
|
*
|
|
* We now expand the partition hierarchy level by level, creating a
|
|
* corresponding hierarchy of AppendRelInfos and RelOptInfos, where each
|
|
* partitioned descendant acts as a parent of its immediate partitions.
|
|
* (This is a difference from what older versions of PostgreSQL did and what
|
|
* is still done in the case of table inheritance for unpartitioned tables,
|
|
* where the hierarchy is flattened during RTE expansion.)
|
|
*
|
|
* PlanRowMarks still carry the top-parent's RTI, and the top-parent's
|
|
* allMarkTypes field still accumulates values from all descendents.
|
|
*
|
|
* "parentrte" and "parentRTindex" are immediate parent's RTE and
|
|
* RTI. "top_parentrc" is top parent's PlanRowMark.
|
|
*
|
|
* The child RangeTblEntry and its RTI are returned in "childrte_p" and
|
|
* "childRTindex_p" resp.
|
|
*/
|
|
static void
|
|
expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
|
|
Index parentRTindex, Relation parentrel,
|
|
PlanRowMark *top_parentrc, Relation childrel,
|
|
RangeTblEntry **childrte_p,
|
|
Index *childRTindex_p)
|
|
{
|
|
Query *parse = root->parse;
|
|
Oid parentOID = RelationGetRelid(parentrel);
|
|
Oid childOID = RelationGetRelid(childrel);
|
|
RangeTblEntry *childrte;
|
|
Index childRTindex;
|
|
AppendRelInfo *appinfo;
|
|
|
|
/*
|
|
* Build an RTE for the child, and attach to query's rangetable list. We
|
|
* copy most fields of the parent's RTE, but replace relation OID,
|
|
* relkind, and inh for the child. Also, set requiredPerms to zero since
|
|
* all required permissions checks are done on the original RTE. Likewise,
|
|
* set the child's securityQuals to empty, because we only want to apply
|
|
* the parent's RLS conditions regardless of what RLS properties
|
|
* individual children may have. (This is an intentional choice to make
|
|
* inherited RLS work like regular permissions checks.) The parent
|
|
* securityQuals will be propagated to children along with other base
|
|
* restriction clauses, so we don't need to do it here.
|
|
*/
|
|
childrte = copyObject(parentrte);
|
|
*childrte_p = childrte;
|
|
childrte->relid = childOID;
|
|
childrte->relkind = childrel->rd_rel->relkind;
|
|
/* A partitioned child will need to be expanded further. */
|
|
if (childrte->relkind == RELKIND_PARTITIONED_TABLE)
|
|
{
|
|
Assert(childOID != parentOID);
|
|
childrte->inh = true;
|
|
}
|
|
else
|
|
childrte->inh = false;
|
|
childrte->requiredPerms = 0;
|
|
childrte->securityQuals = NIL;
|
|
parse->rtable = lappend(parse->rtable, childrte);
|
|
childRTindex = list_length(parse->rtable);
|
|
*childRTindex_p = childRTindex;
|
|
|
|
/*
|
|
* Build an AppendRelInfo struct for each parent/child pair.
|
|
*/
|
|
appinfo = make_append_rel_info(parentrel, childrel,
|
|
parentRTindex, childRTindex);
|
|
root->append_rel_list = lappend(root->append_rel_list, appinfo);
|
|
|
|
/*
|
|
* Translate the column permissions bitmaps to the child's attnums (we
|
|
* have to build the translated_vars list before we can do this). But if
|
|
* this is the parent table, we can leave copyObject's result alone.
|
|
*
|
|
* Note: we need to do this even though the executor won't run any
|
|
* permissions checks on the child RTE. The insertedCols/updatedCols
|
|
* bitmaps may be examined for trigger-firing purposes.
|
|
*/
|
|
if (childOID != parentOID)
|
|
{
|
|
childrte->selectedCols = translate_col_privs(parentrte->selectedCols,
|
|
appinfo->translated_vars);
|
|
childrte->insertedCols = translate_col_privs(parentrte->insertedCols,
|
|
appinfo->translated_vars);
|
|
childrte->updatedCols = translate_col_privs(parentrte->updatedCols,
|
|
appinfo->translated_vars);
|
|
childrte->extraUpdatedCols = translate_col_privs(parentrte->extraUpdatedCols,
|
|
appinfo->translated_vars);
|
|
}
|
|
|
|
/*
|
|
* Store the RTE and appinfo in the respective PlannerInfo arrays, which
|
|
* the caller must already have allocated space for.
|
|
*/
|
|
Assert(childRTindex < root->simple_rel_array_size);
|
|
Assert(root->simple_rte_array[childRTindex] == NULL);
|
|
root->simple_rte_array[childRTindex] = childrte;
|
|
Assert(root->append_rel_array[childRTindex] == NULL);
|
|
root->append_rel_array[childRTindex] = appinfo;
|
|
|
|
/*
|
|
* Build a PlanRowMark if parent is marked FOR UPDATE/SHARE.
|
|
*/
|
|
if (top_parentrc)
|
|
{
|
|
PlanRowMark *childrc = makeNode(PlanRowMark);
|
|
|
|
childrc->rti = childRTindex;
|
|
childrc->prti = top_parentrc->rti;
|
|
childrc->rowmarkId = top_parentrc->rowmarkId;
|
|
/* Reselect rowmark type, because relkind might not match parent */
|
|
childrc->markType = select_rowmark_type(childrte,
|
|
top_parentrc->strength);
|
|
childrc->allMarkTypes = (1 << childrc->markType);
|
|
childrc->strength = top_parentrc->strength;
|
|
childrc->waitPolicy = top_parentrc->waitPolicy;
|
|
|
|
/*
|
|
* We mark RowMarks for partitioned child tables as parent RowMarks so
|
|
* that the executor ignores them (except their existence means that
|
|
* the child tables will be locked using the appropriate mode).
|
|
*/
|
|
childrc->isParent = (childrte->relkind == RELKIND_PARTITIONED_TABLE);
|
|
|
|
/* Include child's rowmark type in top parent's allMarkTypes */
|
|
top_parentrc->allMarkTypes |= childrc->allMarkTypes;
|
|
|
|
root->rowMarks = lappend(root->rowMarks, childrc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* translate_col_privs
|
|
* Translate a bitmapset representing per-column privileges from the
|
|
* parent rel's attribute numbering to the child's.
|
|
*
|
|
* The only surprise here is that we don't translate a parent whole-row
|
|
* reference into a child whole-row reference. That would mean requiring
|
|
* permissions on all child columns, which is overly strict, since the
|
|
* query is really only going to reference the inherited columns. Instead
|
|
* we set the per-column bits for all inherited columns.
|
|
*/
|
|
static Bitmapset *
|
|
translate_col_privs(const Bitmapset *parent_privs,
|
|
List *translated_vars)
|
|
{
|
|
Bitmapset *child_privs = NULL;
|
|
bool whole_row;
|
|
int attno;
|
|
ListCell *lc;
|
|
|
|
/* System attributes have the same numbers in all tables */
|
|
for (attno = FirstLowInvalidHeapAttributeNumber + 1; attno < 0; attno++)
|
|
{
|
|
if (bms_is_member(attno - FirstLowInvalidHeapAttributeNumber,
|
|
parent_privs))
|
|
child_privs = bms_add_member(child_privs,
|
|
attno - FirstLowInvalidHeapAttributeNumber);
|
|
}
|
|
|
|
/* Check if parent has whole-row reference */
|
|
whole_row = bms_is_member(InvalidAttrNumber - FirstLowInvalidHeapAttributeNumber,
|
|
parent_privs);
|
|
|
|
/* And now translate the regular user attributes, using the vars list */
|
|
attno = InvalidAttrNumber;
|
|
foreach(lc, translated_vars)
|
|
{
|
|
Var *var = lfirst_node(Var, lc);
|
|
|
|
attno++;
|
|
if (var == NULL) /* ignore dropped columns */
|
|
continue;
|
|
if (whole_row ||
|
|
bms_is_member(attno - FirstLowInvalidHeapAttributeNumber,
|
|
parent_privs))
|
|
child_privs = bms_add_member(child_privs,
|
|
var->varattno - FirstLowInvalidHeapAttributeNumber);
|
|
}
|
|
|
|
return child_privs;
|
|
}
|
|
|
|
/*
|
|
* expand_appendrel_subquery
|
|
* Add "other rel" RelOptInfos for the children of an appendrel baserel
|
|
*
|
|
* "rel" is a subquery relation that has the rte->inh flag set, meaning it
|
|
* is a UNION ALL subquery that's been flattened into an appendrel, with
|
|
* child subqueries listed in root->append_rel_list. We need to build
|
|
* a RelOptInfo for each child relation so that we can plan scans on them.
|
|
*/
|
|
static void
|
|
expand_appendrel_subquery(PlannerInfo *root, RelOptInfo *rel,
|
|
RangeTblEntry *rte, Index rti)
|
|
{
|
|
ListCell *l;
|
|
|
|
foreach(l, root->append_rel_list)
|
|
{
|
|
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
|
|
Index childRTindex = appinfo->child_relid;
|
|
RangeTblEntry *childrte;
|
|
RelOptInfo *childrel;
|
|
|
|
/* append_rel_list contains all append rels; ignore others */
|
|
if (appinfo->parent_relid != rti)
|
|
continue;
|
|
|
|
/* find the child RTE, which should already exist */
|
|
Assert(childRTindex < root->simple_rel_array_size);
|
|
childrte = root->simple_rte_array[childRTindex];
|
|
Assert(childrte != NULL);
|
|
|
|
/* Build the child RelOptInfo. */
|
|
childrel = build_simple_rel(root, childRTindex, rel);
|
|
|
|
/* Child may itself be an inherited rel, either table or subquery. */
|
|
if (childrte->inh)
|
|
expand_inherited_rtentry(root, childrel, childrte, childRTindex);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* apply_child_basequals
|
|
* Populate childrel's base restriction quals from parent rel's quals,
|
|
* translating them using appinfo.
|
|
*
|
|
* If any of the resulting clauses evaluate to constant false or NULL, we
|
|
* return false and don't apply any quals. Caller should mark the relation as
|
|
* a dummy rel in this case, since it doesn't need to be scanned.
|
|
*/
|
|
bool
|
|
apply_child_basequals(PlannerInfo *root, RelOptInfo *parentrel,
|
|
RelOptInfo *childrel, RangeTblEntry *childRTE,
|
|
AppendRelInfo *appinfo)
|
|
{
|
|
List *childquals;
|
|
Index cq_min_security;
|
|
ListCell *lc;
|
|
|
|
/*
|
|
* The child rel's targetlist might contain non-Var expressions, which
|
|
* means that substitution into the quals could produce opportunities for
|
|
* const-simplification, and perhaps even pseudoconstant quals. Therefore,
|
|
* transform each RestrictInfo separately to see if it reduces to a
|
|
* constant or pseudoconstant. (We must process them separately to keep
|
|
* track of the security level of each qual.)
|
|
*/
|
|
childquals = NIL;
|
|
cq_min_security = UINT_MAX;
|
|
foreach(lc, parentrel->baserestrictinfo)
|
|
{
|
|
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
|
|
Node *childqual;
|
|
ListCell *lc2;
|
|
|
|
Assert(IsA(rinfo, RestrictInfo));
|
|
childqual = adjust_appendrel_attrs(root,
|
|
(Node *) rinfo->clause,
|
|
1, &appinfo);
|
|
childqual = eval_const_expressions(root, childqual);
|
|
/* check for flat-out constant */
|
|
if (childqual && IsA(childqual, Const))
|
|
{
|
|
if (((Const *) childqual)->constisnull ||
|
|
!DatumGetBool(((Const *) childqual)->constvalue))
|
|
{
|
|
/* Restriction reduces to constant FALSE or NULL */
|
|
return false;
|
|
}
|
|
/* Restriction reduces to constant TRUE, so drop it */
|
|
continue;
|
|
}
|
|
/* might have gotten an AND clause, if so flatten it */
|
|
foreach(lc2, make_ands_implicit((Expr *) childqual))
|
|
{
|
|
Node *onecq = (Node *) lfirst(lc2);
|
|
bool pseudoconstant;
|
|
|
|
/* check for pseudoconstant (no Vars or volatile functions) */
|
|
pseudoconstant =
|
|
!contain_vars_of_level(onecq, 0) &&
|
|
!contain_volatile_functions(onecq);
|
|
if (pseudoconstant)
|
|
{
|
|
/* tell createplan.c to check for gating quals */
|
|
root->hasPseudoConstantQuals = true;
|
|
}
|
|
/* reconstitute RestrictInfo with appropriate properties */
|
|
childquals = lappend(childquals,
|
|
make_restrictinfo(root,
|
|
(Expr *) onecq,
|
|
rinfo->is_pushed_down,
|
|
rinfo->outerjoin_delayed,
|
|
pseudoconstant,
|
|
rinfo->security_level,
|
|
NULL, NULL, NULL));
|
|
/* track minimum security level among child quals */
|
|
cq_min_security = Min(cq_min_security, rinfo->security_level);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In addition to the quals inherited from the parent, we might have
|
|
* securityQuals associated with this particular child node. (Currently
|
|
* this can only happen in appendrels originating from UNION ALL;
|
|
* inheritance child tables don't have their own securityQuals, see
|
|
* expand_single_inheritance_child().) Pull any such securityQuals up
|
|
* into the baserestrictinfo for the child. This is similar to
|
|
* process_security_barrier_quals() for the parent rel, except that we
|
|
* can't make any general deductions from such quals, since they don't
|
|
* hold for the whole appendrel.
|
|
*/
|
|
if (childRTE->securityQuals)
|
|
{
|
|
Index security_level = 0;
|
|
|
|
foreach(lc, childRTE->securityQuals)
|
|
{
|
|
List *qualset = (List *) lfirst(lc);
|
|
ListCell *lc2;
|
|
|
|
foreach(lc2, qualset)
|
|
{
|
|
Expr *qual = (Expr *) lfirst(lc2);
|
|
|
|
/* not likely that we'd see constants here, so no check */
|
|
childquals = lappend(childquals,
|
|
make_restrictinfo(root, qual,
|
|
true, false, false,
|
|
security_level,
|
|
NULL, NULL, NULL));
|
|
cq_min_security = Min(cq_min_security, security_level);
|
|
}
|
|
security_level++;
|
|
}
|
|
Assert(security_level <= root->qual_security_level);
|
|
}
|
|
|
|
/*
|
|
* OK, we've got all the baserestrictinfo quals for this child.
|
|
*/
|
|
childrel->baserestrictinfo = childquals;
|
|
childrel->baserestrict_min_security = cq_min_security;
|
|
|
|
return true;
|
|
}
|