diff options
Diffstat (limited to 'src/backend/optimizer/util/appendinfo.c')
-rw-r--r-- | src/backend/optimizer/util/appendinfo.c | 546 |
1 files changed, 400 insertions, 146 deletions
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c index 86922a273c6..af46f581ac1 100644 --- a/src/backend/optimizer/util/appendinfo.c +++ b/src/backend/optimizer/util/appendinfo.c @@ -15,9 +15,12 @@ #include "postgres.h" #include "access/htup_details.h" +#include "access/table.h" +#include "foreign/fdwapi.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/appendinfo.h" +#include "optimizer/pathnode.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -37,8 +40,6 @@ static void make_inh_translation_list(Relation oldrelation, AppendRelInfo *appinfo); static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); -static List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); /* @@ -194,7 +195,6 @@ Node * adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, AppendRelInfo **appinfos) { - Node *result; adjust_appendrel_attrs_context context; context.root = root; @@ -204,40 +204,10 @@ adjust_appendrel_attrs(PlannerInfo *root, Node *node, int nappinfos, /* 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; + /* Should never be translating a Query tree. */ + Assert(node == NULL || !IsA(node, Query)); - 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; + return adjust_appendrel_attrs_mutator(node, &context); } static Node * @@ -343,6 +313,57 @@ adjust_appendrel_attrs_mutator(Node *node, } /* system attributes don't need any other translation */ } + else if (var->varno == ROWID_VAR) + { + /* + * If it's a ROWID_VAR placeholder, see if we've reached a leaf + * target rel, for which we can translate the Var to a specific + * instantiation. We should never be asked to translate to a set + * of relids containing more than one leaf target rel, so the + * answer will be unique. If we're still considering non-leaf + * inheritance levels, return the ROWID_VAR Var as-is. + */ + Relids leaf_result_relids = context->root->leaf_result_relids; + Index leaf_relid = 0; + + for (cnt = 0; cnt < nappinfos; cnt++) + { + if (bms_is_member(appinfos[cnt]->child_relid, + leaf_result_relids)) + { + if (leaf_relid) + elog(ERROR, "cannot translate to multiple leaf relids"); + leaf_relid = appinfos[cnt]->child_relid; + } + } + + if (leaf_relid) + { + RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *) + list_nth(context->root->row_identity_vars, var->varattno - 1); + + if (bms_is_member(leaf_relid, ridinfo->rowidrels)) + { + /* Substitute the Var given in the RowIdentityVarInfo */ + var = copyObject(ridinfo->rowidvar); + /* ... but use the correct relid */ + var->varno = leaf_relid; + /* varnosyn in the RowIdentityVarInfo is probably wrong */ + var->varnosyn = 0; + var->varattnosyn = 0; + } + else + { + /* + * This leaf rel can't return the desired value, so + * substitute a NULL of the correct type. + */ + return (Node *) makeNullConst(var->vartype, + var->vartypmod, + var->varcollid); + } + } + } return (Node *) var; } if (IsA(node, CurrentOfExpr)) @@ -361,44 +382,6 @@ adjust_appendrel_attrs_mutator(Node *node, } 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 */ @@ -486,6 +469,9 @@ adjust_appendrel_attrs_mutator(Node *node, */ Assert(!IsA(node, SubLink)); Assert(!IsA(node, Query)); + /* We should never see these Query substructures, either. */ + Assert(!IsA(node, RangeTblRef)); + Assert(!IsA(node, JoinExpr)); return expression_tree_mutator(node, adjust_appendrel_attrs_mutator, (void *) context); @@ -621,100 +607,101 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, } /* - * 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. + * adjust_inherited_attnums + * Translate an integer list of attribute numbers from parent to child. */ -static List * -adjust_inherited_tlist(List *tlist, AppendRelInfo *context) +List * +adjust_inherited_attnums(List *attnums, AppendRelInfo *context) { - bool changed_it = false; - ListCell *tl; - List *new_tlist; - bool more; - int attrno; + List *result = NIL; + ListCell *lc; /* 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) + /* Look up each attribute in the AppendRelInfo's translated_vars list */ + foreach(lc, attnums) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); + AttrNumber parentattno = lfirst_int(lc); 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)) + if (parentattno <= 0 || + parentattno > 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); + parentattno, get_rel_name(context->parent_reloid)); + childvar = (Var *) list_nth(context->translated_vars, parentattno - 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)); + parentattno, get_rel_name(context->parent_reloid)); - if (tle->resno != childvar->varattno) - { - tle->resno = childvar->varattno; - changed_it = true; - } + result = lappend_int(result, childvar->varattno); } + return result; +} - /* - * 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; +/* + * adjust_inherited_attnums_multilevel + * As above, but traverse multiple inheritance levels as needed. + */ +List * +adjust_inherited_attnums_multilevel(PlannerInfo *root, List *attnums, + Index child_relid, Index top_parent_relid) +{ + AppendRelInfo *appinfo = root->append_rel_array[child_relid]; - new_tlist = NIL; - more = true; - for (attrno = 1; more; attrno++) - { - more = false; - foreach(tl, tlist) - { - TargetEntry *tle = (TargetEntry *) lfirst(tl); + if (!appinfo) + elog(ERROR, "child rel %d not found in append_rel_array", child_relid); - if (tle->resjunk) - continue; /* ignore junk items */ + /* Recurse if immediate parent is not the top parent. */ + if (appinfo->parent_relid != top_parent_relid) + attnums = adjust_inherited_attnums_multilevel(root, attnums, + appinfo->parent_relid, + top_parent_relid); - if (tle->resno == attrno) - new_tlist = lappend(new_tlist, tle); - else if (tle->resno > attrno) - more = true; - } - } + /* Now translate for this child */ + return adjust_inherited_attnums(attnums, appinfo); +} - foreach(tl, tlist) +/* + * get_translated_update_targetlist + * Get the processed_tlist of an UPDATE query, translated as needed to + * match a child target relation. + * + * Optionally also return the list of target column numbers translated + * to this target relation. (The resnos in processed_tlist MUST NOT be + * relied on for this purpose.) + */ +void +get_translated_update_targetlist(PlannerInfo *root, Index relid, + List **processed_tlist, List **update_colnos) +{ + /* This is pretty meaningless for commands other than UPDATE. */ + Assert(root->parse->commandType == CMD_UPDATE); + if (relid == root->parse->resultRelation) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); - - if (!tle->resjunk) - continue; /* here, ignore non-junk items */ - - tle->resno = attrno; - new_tlist = lappend(new_tlist, tle); - attrno++; + /* + * Non-inheritance case, so it's easy. The caller might be expecting + * a tree it can scribble on, though, so copy. + */ + *processed_tlist = copyObject(root->processed_tlist); + if (update_colnos) + *update_colnos = copyObject(root->update_colnos); + } + else + { + Assert(bms_is_member(relid, root->all_result_relids)); + *processed_tlist = (List *) + adjust_appendrel_attrs_multilevel(root, + (Node *) root->processed_tlist, + bms_make_singleton(relid), + bms_make_singleton(root->parse->resultRelation)); + if (update_colnos) + *update_colnos = + adjust_inherited_attnums_multilevel(root, root->update_colnos, + relid, + root->parse->resultRelation); } - - return new_tlist; } /* @@ -746,3 +733,270 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos) } return appinfos; } + + +/***************************************************************************** + * + * ROW-IDENTITY VARIABLE MANAGEMENT + * + * This code lacks a good home, perhaps. We choose to keep it here because + * adjust_appendrel_attrs_mutator() is its principal co-conspirator. That + * function does most of what is needed to expand ROWID_VAR Vars into the + * right things. + * + *****************************************************************************/ + +/* + * add_row_identity_var + * Register a row-identity column to be used in UPDATE/DELETE. + * + * The Var must be equal(), aside from varno, to any other row-identity + * column with the same rowid_name. Thus, for example, "wholerow" + * row identities had better use vartype == RECORDOID. + * + * rtindex is currently redundant with rowid_var->varno, but we specify + * it as a separate parameter in case this is ever generalized to support + * non-Var expressions. (We could reasonably handle expressions over + * Vars of the specified rtindex, but for now that seems unnecessary.) + */ +void +add_row_identity_var(PlannerInfo *root, Var *orig_var, + Index rtindex, const char *rowid_name) +{ + TargetEntry *tle; + Var *rowid_var; + RowIdentityVarInfo *ridinfo; + ListCell *lc; + + /* For now, the argument must be just a Var of the given rtindex */ + Assert(IsA(orig_var, Var)); + Assert(orig_var->varno == rtindex); + Assert(orig_var->varlevelsup == 0); + + /* + * If we're doing non-inherited UPDATE/DELETE, there's little need for + * ROWID_VAR shenanigans. Just shove the presented Var into the + * processed_tlist, and we're done. + */ + if (rtindex == root->parse->resultRelation) + { + tle = makeTargetEntry((Expr *) orig_var, + list_length(root->processed_tlist) + 1, + pstrdup(rowid_name), + true); + root->processed_tlist = lappend(root->processed_tlist, tle); + return; + } + + /* + * Otherwise, rtindex should reference a leaf target relation that's being + * added to the query during expand_inherited_rtentry(). + */ + Assert(bms_is_member(rtindex, root->leaf_result_relids)); + Assert(root->append_rel_array[rtindex] != NULL); + + /* + * We have to find a matching RowIdentityVarInfo, or make one if there is + * none. To allow using equal() to match the vars, change the varno to + * ROWID_VAR, leaving all else alone. + */ + rowid_var = copyObject(orig_var); + /* This could eventually become ChangeVarNodes() */ + rowid_var->varno = ROWID_VAR; + + /* Look for an existing row-id column of the same name */ + foreach(lc, root->row_identity_vars) + { + ridinfo = (RowIdentityVarInfo *) lfirst(lc); + if (strcmp(rowid_name, ridinfo->rowidname) != 0) + continue; + if (equal(rowid_var, ridinfo->rowidvar)) + { + /* Found a match; we need only record that rtindex needs it too */ + ridinfo->rowidrels = bms_add_member(ridinfo->rowidrels, rtindex); + return; + } + else + { + /* Ooops, can't handle this */ + elog(ERROR, "conflicting uses of row-identity name \"%s\"", + rowid_name); + } + } + + /* No request yet, so add a new RowIdentityVarInfo */ + ridinfo = makeNode(RowIdentityVarInfo); + ridinfo->rowidvar = copyObject(rowid_var); + /* for the moment, estimate width using just the datatype info */ + ridinfo->rowidwidth = get_typavgwidth(exprType((Node *) rowid_var), + exprTypmod((Node *) rowid_var)); + ridinfo->rowidname = pstrdup(rowid_name); + ridinfo->rowidrels = bms_make_singleton(rtindex); + + root->row_identity_vars = lappend(root->row_identity_vars, ridinfo); + + /* Change rowid_var into a reference to this row_identity_vars entry */ + rowid_var->varattno = list_length(root->row_identity_vars); + + /* Push the ROWID_VAR reference variable into processed_tlist */ + tle = makeTargetEntry((Expr *) rowid_var, + list_length(root->processed_tlist) + 1, + pstrdup(rowid_name), + true); + root->processed_tlist = lappend(root->processed_tlist, tle); +} + +/* + * add_row_identity_columns + * + * This function adds the row identity columns needed by the core code. + * FDWs might call add_row_identity_var() for themselves to add nonstandard + * columns. (Duplicate requests are fine.) + */ +void +add_row_identity_columns(PlannerInfo *root, Index rtindex, + RangeTblEntry *target_rte, + Relation target_relation) +{ + CmdType commandType = root->parse->commandType; + char relkind = target_relation->rd_rel->relkind; + Var *var; + + Assert(commandType == CMD_UPDATE || commandType == CMD_DELETE); + + if (relkind == RELKIND_RELATION || + relkind == RELKIND_MATVIEW || + relkind == RELKIND_PARTITIONED_TABLE) + { + /* + * Emit CTID so that executor can find the row to update or delete. + */ + var = makeVar(rtindex, + SelfItemPointerAttributeNumber, + TIDOID, + -1, + InvalidOid, + 0); + add_row_identity_var(root, var, rtindex, "ctid"); + } + else if (relkind == RELKIND_FOREIGN_TABLE) + { + /* + * Let the foreign table's FDW add whatever junk TLEs it wants. + */ + FdwRoutine *fdwroutine; + + fdwroutine = GetFdwRoutineForRelation(target_relation, false); + + if (fdwroutine->AddForeignUpdateTargets != NULL) + fdwroutine->AddForeignUpdateTargets(root, rtindex, + target_rte, target_relation); + + /* + * For UPDATE, we need to make the FDW fetch unchanged columns by + * asking it to fetch a whole-row Var. That's because the top-level + * targetlist only contains entries for changed columns, but + * ExecUpdate will need to build the complete new tuple. (Actually, + * we only really need this in UPDATEs that are not pushed to the + * remote side, but it's hard to tell if that will be the case at the + * point when this function is called.) + * + * We will also need the whole row if there are any row triggers, so + * that the executor will have the "old" row to pass to the trigger. + * Alas, this misses system columns. + */ + if (commandType == CMD_UPDATE || + (target_relation->trigdesc && + (target_relation->trigdesc->trig_delete_after_row || + target_relation->trigdesc->trig_delete_before_row))) + { + var = makeVar(rtindex, + InvalidAttrNumber, + RECORDOID, + -1, + InvalidOid, + 0); + add_row_identity_var(root, var, rtindex, "wholerow"); + } + } +} + +/* + * distribute_row_identity_vars + * + * After we have finished identifying all the row identity columns + * needed by an inherited UPDATE/DELETE query, make sure that these + * columns will be generated by all the target relations. + * + * This is more or less like what build_base_rel_tlists() does, + * except that it would not understand what to do with ROWID_VAR Vars. + * Since that function runs before inheritance relations are expanded, + * it will never see any such Vars anyway. + */ +void +distribute_row_identity_vars(PlannerInfo *root) +{ + Query *parse = root->parse; + int result_relation = parse->resultRelation; + RangeTblEntry *target_rte; + RelOptInfo *target_rel; + ListCell *lc; + + /* There's nothing to do if this isn't an inherited UPDATE/DELETE. */ + if (parse->commandType != CMD_UPDATE && parse->commandType != CMD_DELETE) + { + Assert(root->row_identity_vars == NIL); + return; + } + target_rte = rt_fetch(result_relation, parse->rtable); + if (!target_rte->inh) + { + Assert(root->row_identity_vars == NIL); + return; + } + + /* + * Ordinarily, we expect that leaf result relation(s) will have added some + * ROWID_VAR Vars to the query. However, it's possible that constraint + * exclusion suppressed every leaf relation. The executor will get upset + * if the plan has no row identity columns at all, even though it will + * certainly process no rows. Handle this edge case by re-opening the top + * result relation and adding the row identity columns it would have used, + * as preprocess_targetlist() would have done if it weren't marked "inh". + * (This is a bit ugly, but it seems better to confine the ugliness and + * extra cycles to this unusual corner case.) We needn't worry about + * fixing the rel's reltarget, as that won't affect the finished plan. + */ + if (root->row_identity_vars == NIL) + { + Relation target_relation; + + target_relation = table_open(target_rte->relid, NoLock); + add_row_identity_columns(root, result_relation, + target_rte, target_relation); + table_close(target_relation, NoLock); + return; + } + + /* + * Dig through the processed_tlist to find the ROWID_VAR reference Vars, + * and forcibly copy them into the reltarget list of the topmost target + * relation. That's sufficient because they'll be copied to the + * individual leaf target rels (with appropriate translation) later, + * during appendrel expansion --- see set_append_rel_size(). + */ + target_rel = find_base_rel(root, result_relation); + + foreach(lc, root->processed_tlist) + { + TargetEntry *tle = lfirst(lc); + Var *var = (Var *) tle->expr; + + if (var && IsA(var, Var) && var->varno == ROWID_VAR) + { + target_rel->reltarget->exprs = + lappend(target_rel->reltarget->exprs, copyObject(var)); + /* reltarget cost and width will be computed later */ + } + } +} |