postgresql/src/backend/optimizer/util/appendinfo.c

749 lines
21 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* appendinfo.c
* Routines for mapping between append parent(s) and children
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/optimizer/path/appendinfo.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/appendinfo.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
typedef struct
{
PlannerInfo *root;
int nappinfos;
AppendRelInfo **appinfos;
} adjust_appendrel_attrs_context;
static void make_inh_translation_list(Relation oldrelation,
Relation newrelation,
Index newvarno,
AppendRelInfo *appinfo);
static Node *adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context);
static List *adjust_inherited_tlist(List *tlist,
AppendRelInfo *context);
/*
* make_append_rel_info
* Build an AppendRelInfo for the parent-child pair
*/
AppendRelInfo *
make_append_rel_info(Relation parentrel, Relation childrel,
Index parentRTindex, Index childRTindex)
{
AppendRelInfo *appinfo = makeNode(AppendRelInfo);
appinfo->parent_relid = parentRTindex;
appinfo->child_relid = childRTindex;
appinfo->parent_reltype = parentrel->rd_rel->reltype;
appinfo->child_reltype = childrel->rd_rel->reltype;
make_inh_translation_list(parentrel, childrel, childRTindex, appinfo);
appinfo->parent_reloid = RelationGetRelid(parentrel);
return appinfo;
}
/*
* make_inh_translation_list
* Build the list of translations from parent Vars to child Vars for
* an inheritance child, as well as a reverse-translation array.
*
* The reverse-translation array has an entry for each child relation
* column, which is either the 1-based index of the corresponding parent
* column, or 0 if there's no match (that happens for dropped child columns,
* as well as child columns beyond those of the parent, which are allowed in
* traditional inheritance though not partitioning).
*
* For paranoia's sake, we match type/collation as well as attribute name.
*/
static void
make_inh_translation_list(Relation oldrelation, Relation newrelation,
Index newvarno,
AppendRelInfo *appinfo)
{
List *vars = NIL;
AttrNumber *pcolnos;
TupleDesc old_tupdesc = RelationGetDescr(oldrelation);
TupleDesc new_tupdesc = RelationGetDescr(newrelation);
Oid new_relid = RelationGetRelid(newrelation);
int oldnatts = old_tupdesc->natts;
int newnatts = new_tupdesc->natts;
int old_attno;
int new_attno = 0;
/* Initialize reverse-translation array with all entries zero */
appinfo->num_child_cols = newnatts;
appinfo->parent_colnos = pcolnos =
(AttrNumber *) palloc0(newnatts * sizeof(AttrNumber));
for (old_attno = 0; old_attno < oldnatts; old_attno++)
{
Form_pg_attribute att;
char *attname;
Oid atttypid;
int32 atttypmod;
Oid attcollation;
att = TupleDescAttr(old_tupdesc, old_attno);
if (att->attisdropped)
{
/* Just put NULL into this list entry */
vars = lappend(vars, NULL);
continue;
}
attname = NameStr(att->attname);
atttypid = att->atttypid;
atttypmod = att->atttypmod;
attcollation = att->attcollation;
/*
* When we are generating the "translation list" for the parent table
* of an inheritance set, no need to search for matches.
*/
if (oldrelation == newrelation)
{
vars = lappend(vars, makeVar(newvarno,
(AttrNumber) (old_attno + 1),
atttypid,
atttypmod,
attcollation,
0));
pcolnos[old_attno] = old_attno + 1;
continue;
}
/*
* Otherwise we have to search for the matching column by name.
* There's no guarantee it'll have the same column position, because
* of cases like ALTER TABLE ADD COLUMN and multiple inheritance.
* However, in simple cases, the relative order of columns is mostly
* the same in both relations, so try the column of newrelation that
* follows immediately after the one that we just found, and if that
* fails, let syscache handle it.
*/
if (new_attno >= newnatts ||
(att = TupleDescAttr(new_tupdesc, new_attno))->attisdropped ||
strcmp(attname, NameStr(att->attname)) != 0)
{
HeapTuple newtup;
newtup = SearchSysCacheAttName(new_relid, attname);
if (!HeapTupleIsValid(newtup))
elog(ERROR, "could not find inherited attribute \"%s\" of relation \"%s\"",
attname, RelationGetRelationName(newrelation));
new_attno = ((Form_pg_attribute) GETSTRUCT(newtup))->attnum - 1;
Assert(new_attno >= 0 && new_attno < newnatts);
ReleaseSysCache(newtup);
att = TupleDescAttr(new_tupdesc, new_attno);
}
/* Found it, check type and collation match */
if (atttypid != att->atttypid || atttypmod != att->atttypmod)
elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's type",
attname, RelationGetRelationName(newrelation));
if (attcollation != att->attcollation)
elog(ERROR, "attribute \"%s\" of relation \"%s\" does not match parent's collation",
attname, RelationGetRelationName(newrelation));
vars = lappend(vars, makeVar(newvarno,
(AttrNumber) (new_attno + 1),
atttypid,
atttypmod,
attcollation,
0));
pcolnos[new_attno] = old_attno + 1;
new_attno++;
}
appinfo->translated_vars = vars;
}
/*
* adjust_appendrel_attrs
* Copy the specified query or expression and translate Vars referring to a
* parent rel to refer to the corresponding 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 not hugely different from what pullup_replace_vars() does;
* maybe we should try to fold the two routines together.
*/
Node *
adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos,
AppendRelInfo **appinfos)
{
Node *result;
adjust_appendrel_attrs_context context;
context.root = root;
context.nappinfos = nappinfos;
context.appinfos = appinfos;
/* If there's nothing to adjust, don't call this function. */
Assert(nappinfos >= 1 && appinfos != NULL);
/*
* Must be prepared to start with a Query or a bare expression tree.
*/
if (node && IsA(node, Query))
{
Query *newnode;
int cnt;
newnode = query_tree_mutator((Query *) node,
adjust_appendrel_attrs_mutator,
(void *) &context,
QTW_IGNORE_RC_SUBQUERIES);
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
if (newnode->resultRelation == appinfo->parent_relid)
{
newnode->resultRelation = appinfo->child_relid;
/* Fix tlist resnos too, if it's inherited UPDATE */
if (newnode->commandType == CMD_UPDATE)
newnode->targetList =
adjust_inherited_tlist(newnode->targetList,
appinfo);
break;
}
}
result = (Node *) newnode;
}
else
result = adjust_appendrel_attrs_mutator(node, &context);
return result;
}
static Node *
adjust_appendrel_attrs_mutator(Node *node,
adjust_appendrel_attrs_context *context)
{
AppendRelInfo **appinfos = context->appinfos;
int nappinfos = context->nappinfos;
int cnt;
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) copyObject(node);
AppendRelInfo *appinfo = NULL;
Reconsider the representation of join alias Vars. The core idea of this patch is to make the parser generate join alias Vars (that is, ones with varno pointing to a JOIN RTE) only when the alias Var is actually different from any raw join input, that is a type coercion and/or COALESCE is necessary to generate the join output value. Otherwise just generate varno/varattno pointing to the relevant join input column. In effect, this means that the planner's flatten_join_alias_vars() transformation is already done in the parser, for all cases except (a) columns that are merged by JOIN USING and are transformed in the process, and (b) whole-row join Vars. In principle that would allow us to skip doing flatten_join_alias_vars() in many more queries than we do now, but we don't have quite enough infrastructure to know that we can do so --- in particular there's no cheap way to know whether there are any whole-row join Vars. I'm not sure if it's worth the trouble to add a Query-level flag for that, and in any case it seems like fit material for a separate patch. But even without skipping the work entirely, this should make flatten_join_alias_vars() faster, particularly where there are nested joins that it previously had to flatten recursively. An essential part of this change is to replace Var nodes' varnoold/varoattno fields with varnosyn/varattnosyn, which have considerably more tightly-defined meanings than the old fields: when they differ from varno/varattno, they identify the Var's position in an aliased JOIN RTE, and the join alias is what ruleutils.c should print for the Var. This is necessary because the varno change destroyed ruleutils.c's ability to find the JOIN RTE from the Var's varno. Another way in which this change broke ruleutils.c is that it's no longer feasible to determine, from a JOIN RTE's joinaliasvars list, which join columns correspond to which columns of the join's immediate input relations. (If those are sub-joins, the joinaliasvars entries may point to columns of their base relations, not the sub-joins.) But that was a horrid mess requiring a lot of fragile assumptions already, so let's just bite the bullet and add some more JOIN RTE fields to make it more straightforward to figure that out. I added two integer-List fields containing the relevant column numbers from the left and right input rels, plus a count of how many merged columns there are. This patch depends on the ParseNamespaceColumn infrastructure that I added in commit 5815696bc. The biggest bit of code change is restructuring transformFromClauseItem's handling of JOINs so that the ParseNamespaceColumn data is propagated upward correctly. Other than that and the ruleutils fixes, everything pretty much just works, though some processing is now inessential. I grabbed two pieces of low-hanging fruit in that line: 1. In find_expr_references, we don't need to recurse into join alias Vars anymore. There aren't any except for references to merged USING columns, which are more properly handled when we scan the join's RTE. This change actually fixes an edge-case issue: we will now record a dependency on any type-coercion function present in a USING column's joinaliasvar, even if that join column has no references in the query text. The odds of the missing dependency causing a problem seem quite small: you'd have to posit somebody dropping an implicit cast between two data types, without removing the types themselves, and then having a stored rule containing a whole-row Var for a join whose USING merge depends on that cast. So I don't feel a great need to change this in the back branches. But in theory this way is more correct. 2. markRTEForSelectPriv and markTargetListOrigin don't need to recurse into join alias Vars either, because the cases they care about don't apply to alias Vars for USING columns that are semantically distinct from the underlying columns. This removes the only case in which markVarForSelectPriv could be called with NULL for the RTE, so adjust the comments to describe that hack as being strictly internal to markRTEForSelectPriv. catversion bump required due to changes in stored rules. Discussion: https://postgr.es/m/7115.1577986646@sss.pgh.pa.us
2020-01-09 17:56:59 +01:00
if (var->varlevelsup != 0)
return (Node *) var; /* no changes needed */
for (cnt = 0; cnt < nappinfos; cnt++)
{
if (var->varno == appinfos[cnt]->parent_relid)
{
appinfo = appinfos[cnt];
break;
}
}
Reconsider the representation of join alias Vars. The core idea of this patch is to make the parser generate join alias Vars (that is, ones with varno pointing to a JOIN RTE) only when the alias Var is actually different from any raw join input, that is a type coercion and/or COALESCE is necessary to generate the join output value. Otherwise just generate varno/varattno pointing to the relevant join input column. In effect, this means that the planner's flatten_join_alias_vars() transformation is already done in the parser, for all cases except (a) columns that are merged by JOIN USING and are transformed in the process, and (b) whole-row join Vars. In principle that would allow us to skip doing flatten_join_alias_vars() in many more queries than we do now, but we don't have quite enough infrastructure to know that we can do so --- in particular there's no cheap way to know whether there are any whole-row join Vars. I'm not sure if it's worth the trouble to add a Query-level flag for that, and in any case it seems like fit material for a separate patch. But even without skipping the work entirely, this should make flatten_join_alias_vars() faster, particularly where there are nested joins that it previously had to flatten recursively. An essential part of this change is to replace Var nodes' varnoold/varoattno fields with varnosyn/varattnosyn, which have considerably more tightly-defined meanings than the old fields: when they differ from varno/varattno, they identify the Var's position in an aliased JOIN RTE, and the join alias is what ruleutils.c should print for the Var. This is necessary because the varno change destroyed ruleutils.c's ability to find the JOIN RTE from the Var's varno. Another way in which this change broke ruleutils.c is that it's no longer feasible to determine, from a JOIN RTE's joinaliasvars list, which join columns correspond to which columns of the join's immediate input relations. (If those are sub-joins, the joinaliasvars entries may point to columns of their base relations, not the sub-joins.) But that was a horrid mess requiring a lot of fragile assumptions already, so let's just bite the bullet and add some more JOIN RTE fields to make it more straightforward to figure that out. I added two integer-List fields containing the relevant column numbers from the left and right input rels, plus a count of how many merged columns there are. This patch depends on the ParseNamespaceColumn infrastructure that I added in commit 5815696bc. The biggest bit of code change is restructuring transformFromClauseItem's handling of JOINs so that the ParseNamespaceColumn data is propagated upward correctly. Other than that and the ruleutils fixes, everything pretty much just works, though some processing is now inessential. I grabbed two pieces of low-hanging fruit in that line: 1. In find_expr_references, we don't need to recurse into join alias Vars anymore. There aren't any except for references to merged USING columns, which are more properly handled when we scan the join's RTE. This change actually fixes an edge-case issue: we will now record a dependency on any type-coercion function present in a USING column's joinaliasvar, even if that join column has no references in the query text. The odds of the missing dependency causing a problem seem quite small: you'd have to posit somebody dropping an implicit cast between two data types, without removing the types themselves, and then having a stored rule containing a whole-row Var for a join whose USING merge depends on that cast. So I don't feel a great need to change this in the back branches. But in theory this way is more correct. 2. markRTEForSelectPriv and markTargetListOrigin don't need to recurse into join alias Vars either, because the cases they care about don't apply to alias Vars for USING columns that are semantically distinct from the underlying columns. This removes the only case in which markVarForSelectPriv could be called with NULL for the RTE, so adjust the comments to describe that hack as being strictly internal to markRTEForSelectPriv. catversion bump required due to changes in stored rules. Discussion: https://postgr.es/m/7115.1577986646@sss.pgh.pa.us
2020-01-09 17:56:59 +01:00
if (appinfo)
{
var->varno = appinfo->child_relid;
Reconsider the representation of join alias Vars. The core idea of this patch is to make the parser generate join alias Vars (that is, ones with varno pointing to a JOIN RTE) only when the alias Var is actually different from any raw join input, that is a type coercion and/or COALESCE is necessary to generate the join output value. Otherwise just generate varno/varattno pointing to the relevant join input column. In effect, this means that the planner's flatten_join_alias_vars() transformation is already done in the parser, for all cases except (a) columns that are merged by JOIN USING and are transformed in the process, and (b) whole-row join Vars. In principle that would allow us to skip doing flatten_join_alias_vars() in many more queries than we do now, but we don't have quite enough infrastructure to know that we can do so --- in particular there's no cheap way to know whether there are any whole-row join Vars. I'm not sure if it's worth the trouble to add a Query-level flag for that, and in any case it seems like fit material for a separate patch. But even without skipping the work entirely, this should make flatten_join_alias_vars() faster, particularly where there are nested joins that it previously had to flatten recursively. An essential part of this change is to replace Var nodes' varnoold/varoattno fields with varnosyn/varattnosyn, which have considerably more tightly-defined meanings than the old fields: when they differ from varno/varattno, they identify the Var's position in an aliased JOIN RTE, and the join alias is what ruleutils.c should print for the Var. This is necessary because the varno change destroyed ruleutils.c's ability to find the JOIN RTE from the Var's varno. Another way in which this change broke ruleutils.c is that it's no longer feasible to determine, from a JOIN RTE's joinaliasvars list, which join columns correspond to which columns of the join's immediate input relations. (If those are sub-joins, the joinaliasvars entries may point to columns of their base relations, not the sub-joins.) But that was a horrid mess requiring a lot of fragile assumptions already, so let's just bite the bullet and add some more JOIN RTE fields to make it more straightforward to figure that out. I added two integer-List fields containing the relevant column numbers from the left and right input rels, plus a count of how many merged columns there are. This patch depends on the ParseNamespaceColumn infrastructure that I added in commit 5815696bc. The biggest bit of code change is restructuring transformFromClauseItem's handling of JOINs so that the ParseNamespaceColumn data is propagated upward correctly. Other than that and the ruleutils fixes, everything pretty much just works, though some processing is now inessential. I grabbed two pieces of low-hanging fruit in that line: 1. In find_expr_references, we don't need to recurse into join alias Vars anymore. There aren't any except for references to merged USING columns, which are more properly handled when we scan the join's RTE. This change actually fixes an edge-case issue: we will now record a dependency on any type-coercion function present in a USING column's joinaliasvar, even if that join column has no references in the query text. The odds of the missing dependency causing a problem seem quite small: you'd have to posit somebody dropping an implicit cast between two data types, without removing the types themselves, and then having a stored rule containing a whole-row Var for a join whose USING merge depends on that cast. So I don't feel a great need to change this in the back branches. But in theory this way is more correct. 2. markRTEForSelectPriv and markTargetListOrigin don't need to recurse into join alias Vars either, because the cases they care about don't apply to alias Vars for USING columns that are semantically distinct from the underlying columns. This removes the only case in which markVarForSelectPriv could be called with NULL for the RTE, so adjust the comments to describe that hack as being strictly internal to markRTEForSelectPriv. catversion bump required due to changes in stored rules. Discussion: https://postgr.es/m/7115.1577986646@sss.pgh.pa.us
2020-01-09 17:56:59 +01:00
/* it's now a generated Var, so drop any syntactic labeling */
var->varnosyn = 0;
var->varattnosyn = 0;
if (var->varattno > 0)
{
Node *newnode;
if (var->varattno > list_length(appinfo->translated_vars))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
var->varattno, get_rel_name(appinfo->parent_reloid));
newnode = copyObject(list_nth(appinfo->translated_vars,
var->varattno - 1));
if (newnode == NULL)
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
var->varattno, get_rel_name(appinfo->parent_reloid));
return newnode;
}
else if (var->varattno == 0)
{
/*
* Whole-row Var: if we are dealing with named rowtypes, we
* can use a whole-row Var for the child table plus a coercion
* step to convert the tuple layout to the parent's rowtype.
* Otherwise we have to generate a RowExpr.
*/
if (OidIsValid(appinfo->child_reltype))
{
Assert(var->vartype == appinfo->parent_reltype);
if (appinfo->parent_reltype != appinfo->child_reltype)
{
ConvertRowtypeExpr *r = makeNode(ConvertRowtypeExpr);
r->arg = (Expr *) var;
r->resulttype = appinfo->parent_reltype;
r->convertformat = COERCE_IMPLICIT_CAST;
r->location = -1;
/* Make sure the Var node has the right type ID, too */
var->vartype = appinfo->child_reltype;
return (Node *) r;
}
}
else
{
/*
* Build a RowExpr containing the translated variables.
*
* In practice var->vartype will always be RECORDOID here,
* so we need to come up with some suitable column names.
* We use the parent RTE's column names.
*
* Note: we can't get here for inheritance cases, so there
* is no need to worry that translated_vars might contain
* some dummy NULLs.
*/
RowExpr *rowexpr;
List *fields;
RangeTblEntry *rte;
rte = rt_fetch(appinfo->parent_relid,
context->root->parse->rtable);
fields = copyObject(appinfo->translated_vars);
rowexpr = makeNode(RowExpr);
rowexpr->args = fields;
rowexpr->row_typeid = var->vartype;
rowexpr->row_format = COERCE_IMPLICIT_CAST;
rowexpr->colnames = copyObject(rte->eref->colnames);
rowexpr->location = -1;
return (Node *) rowexpr;
}
}
/* system attributes don't need any other translation */
}
return (Node *) var;
}
if (IsA(node, CurrentOfExpr))
{
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
if (cexpr->cvarno == appinfo->parent_relid)
{
cexpr->cvarno = appinfo->child_relid;
break;
}
}
return (Node *) cexpr;
}
if (IsA(node, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) copyObject(node);
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
if (rtr->rtindex == appinfo->parent_relid)
{
rtr->rtindex = appinfo->child_relid;
break;
}
}
return (Node *) rtr;
}
if (IsA(node, JoinExpr))
{
/* Copy the JoinExpr node with correct mutation of subnodes */
JoinExpr *j;
AppendRelInfo *appinfo;
j = (JoinExpr *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix JoinExpr's rtindex (probably never happens) */
for (cnt = 0; cnt < nappinfos; cnt++)
{
appinfo = appinfos[cnt];
if (j->rtindex == appinfo->parent_relid)
{
j->rtindex = appinfo->child_relid;
break;
}
}
return (Node *) j;
}
if (IsA(node, PlaceHolderVar))
{
/* Copy the PlaceHolderVar node with correct mutation of subnodes */
PlaceHolderVar *phv;
phv = (PlaceHolderVar *) expression_tree_mutator(node,
adjust_appendrel_attrs_mutator,
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos,
context->appinfos);
return (Node *) phv;
}
/* Shouldn't need to handle planner auxiliary nodes here */
Assert(!IsA(node, SpecialJoinInfo));
Assert(!IsA(node, AppendRelInfo));
Assert(!IsA(node, PlaceHolderInfo));
Assert(!IsA(node, MinMaxAggInfo));
/*
* We have to process RestrictInfo nodes specially. (Note: although
* set_append_rel_pathlist will hide RestrictInfos in the parent's
* baserestrictinfo list from us, it doesn't hide those in joininfo.)
*/
if (IsA(node, RestrictInfo))
{
RestrictInfo *oldinfo = (RestrictInfo *) node;
RestrictInfo *newinfo = makeNode(RestrictInfo);
/* Copy all flat-copiable fields */
memcpy(newinfo, oldinfo, sizeof(RestrictInfo));
/* Recursively fix the clause itself */
newinfo->clause = (Expr *)
adjust_appendrel_attrs_mutator((Node *) oldinfo->clause, context);
/* and the modified version, if an OR clause */
newinfo->orclause = (Expr *)
adjust_appendrel_attrs_mutator((Node *) oldinfo->orclause, context);
/* adjust relid sets too */
newinfo->clause_relids = adjust_child_relids(oldinfo->clause_relids,
context->nappinfos,
context->appinfos);
newinfo->required_relids = adjust_child_relids(oldinfo->required_relids,
context->nappinfos,
context->appinfos);
newinfo->outer_relids = adjust_child_relids(oldinfo->outer_relids,
context->nappinfos,
context->appinfos);
newinfo->nullable_relids = adjust_child_relids(oldinfo->nullable_relids,
context->nappinfos,
context->appinfos);
newinfo->left_relids = adjust_child_relids(oldinfo->left_relids,
context->nappinfos,
context->appinfos);
newinfo->right_relids = adjust_child_relids(oldinfo->right_relids,
context->nappinfos,
context->appinfos);
/*
* Reset cached derivative fields, since these might need to have
* different values when considering the child relation. Note we
* don't reset left_ec/right_ec: each child variable is implicitly
* equivalent to its parent, so still a member of the same EC if any.
*/
newinfo->eval_cost.startup = -1;
newinfo->norm_selec = -1;
newinfo->outer_selec = -1;
newinfo->left_em = NULL;
newinfo->right_em = NULL;
newinfo->scansel_cache = NIL;
newinfo->left_bucketsize = -1;
newinfo->right_bucketsize = -1;
newinfo->left_mcvfreq = -1;
newinfo->right_mcvfreq = -1;
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));
return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
(void *) context);
}
/*
* adjust_appendrel_attrs_multilevel
* Apply Var translations from a toplevel appendrel parent down to a child.
*
* In some cases we need to translate expressions referencing a parent relation
* to reference an appendrel child that's multiple levels removed from it.
*/
Node *
adjust_appendrel_attrs_multilevel(PlannerInfo *root, Node *node,
Relids child_relids,
Relids top_parent_relids)
{
AppendRelInfo **appinfos;
Bitmapset *parent_relids = NULL;
int nappinfos;
int cnt;
Assert(bms_num_members(child_relids) == bms_num_members(top_parent_relids));
appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
/* Construct relids set for the immediate parent of given child. */
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
}
/* Recurse if immediate parent is not the top parent. */
if (!bms_equal(parent_relids, top_parent_relids))
node = adjust_appendrel_attrs_multilevel(root, node, parent_relids,
top_parent_relids);
/* Now translate for this child */
node = adjust_appendrel_attrs(root, node, nappinfos, appinfos);
pfree(appinfos);
return node;
}
/*
* Substitute child relids for parent relids in a Relid set. The array of
* appinfos specifies the substitutions to be performed.
*/
Relids
adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos)
{
Bitmapset *result = NULL;
int cnt;
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
/* Remove parent, add child */
if (bms_is_member(appinfo->parent_relid, relids))
{
/* Make a copy if we are changing the set. */
if (!result)
result = bms_copy(relids);
result = bms_del_member(result, appinfo->parent_relid);
result = bms_add_member(result, appinfo->child_relid);
}
}
/* If we made any changes, return the modified copy. */
if (result)
return result;
/* Otherwise, return the original set without modification. */
return relids;
}
/*
* Replace any relid present in top_parent_relids with its child in
* child_relids. Members of child_relids can be multiple levels below top
* parent in the partition hierarchy.
*/
Relids
adjust_child_relids_multilevel(PlannerInfo *root, Relids relids,
Relids child_relids, Relids top_parent_relids)
{
AppendRelInfo **appinfos;
int nappinfos;
Relids parent_relids = NULL;
Relids result;
Relids tmp_result = NULL;
int cnt;
/*
* If the given relids set doesn't contain any of the top parent relids,
* it will remain unchanged.
*/
if (!bms_overlap(relids, top_parent_relids))
return relids;
appinfos = find_appinfos_by_relids(root, child_relids, &nappinfos);
/* Construct relids set for the immediate parent of the given child. */
for (cnt = 0; cnt < nappinfos; cnt++)
{
AppendRelInfo *appinfo = appinfos[cnt];
parent_relids = bms_add_member(parent_relids, appinfo->parent_relid);
}
/* Recurse if immediate parent is not the top parent. */
if (!bms_equal(parent_relids, top_parent_relids))
{
tmp_result = adjust_child_relids_multilevel(root, relids,
parent_relids,
top_parent_relids);
relids = tmp_result;
}
result = adjust_child_relids(relids, nappinfos, appinfos);
/* Free memory consumed by any intermediate result. */
if (tmp_result)
bms_free(tmp_result);
bms_free(parent_relids);
pfree(appinfos);
return result;
}
/*
* Adjust the targetlist entries of an inherited UPDATE operation
*
* The expressions have already been fixed, but we have to make sure that
* the target resnos match the child table (they may not, in the case of
* a column that was added after-the-fact by ALTER TABLE). In some cases
* this can force us to re-order the tlist to preserve resno ordering.
* (We do all this work in special cases so that preptlist.c is fast for
* the typical case.)
*
* The given tlist has already been through expression_tree_mutator;
* therefore the TargetEntry nodes are fresh copies that it's okay to
* scribble on.
*
* Note that this is not needed for INSERT because INSERT isn't inheritable.
*/
static List *
adjust_inherited_tlist(List *tlist, AppendRelInfo *context)
{
bool changed_it = false;
ListCell *tl;
List *new_tlist;
bool more;
int attrno;
/* This should only happen for an inheritance case, not UNION ALL */
Assert(OidIsValid(context->parent_reloid));
/* Scan tlist and update resnos to match attnums of child rel */
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
Var *childvar;
if (tle->resjunk)
continue; /* ignore junk items */
/* Look up the translation of this column: it must be a Var */
if (tle->resno <= 0 ||
tle->resno > list_length(context->translated_vars))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
tle->resno, get_rel_name(context->parent_reloid));
childvar = (Var *) list_nth(context->translated_vars, tle->resno - 1);
if (childvar == NULL || !IsA(childvar, Var))
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
tle->resno, get_rel_name(context->parent_reloid));
if (tle->resno != childvar->varattno)
{
tle->resno = childvar->varattno;
changed_it = true;
}
}
/*
* If we changed anything, re-sort the tlist by resno, and make sure
* resjunk entries have resnos above the last real resno. The sort
* algorithm is a bit stupid, but for such a seldom-taken path, small is
* probably better than fast.
*/
if (!changed_it)
return tlist;
new_tlist = NIL;
more = true;
for (attrno = 1; more; attrno++)
{
more = false;
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
if (tle->resjunk)
continue; /* ignore junk items */
if (tle->resno == attrno)
new_tlist = lappend(new_tlist, tle);
else if (tle->resno > attrno)
more = true;
}
}
foreach(tl, tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(tl);
if (!tle->resjunk)
continue; /* here, ignore non-junk items */
tle->resno = attrno;
new_tlist = lappend(new_tlist, tle);
attrno++;
}
return new_tlist;
}
/*
* find_appinfos_by_relids
* Find AppendRelInfo structures for all relations specified by relids.
*
* The AppendRelInfos are returned in an array, which can be pfree'd by the
* caller. *nappinfos is set to the number of entries in the array.
*/
AppendRelInfo **
find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
{
AppendRelInfo **appinfos;
int cnt = 0;
int i;
*nappinfos = bms_num_members(relids);
appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos);
i = -1;
while ((i = bms_next_member(relids, i)) >= 0)
{
AppendRelInfo *appinfo = root->append_rel_array[i];
if (!appinfo)
elog(ERROR, "child rel %d not found in append_rel_array", i);
appinfos[cnt++] = appinfo;
}
return appinfos;
}