mirror of
https://github.com/postgres/postgres.git
synced 2026-10-25 01:39:28 +03:00
Perform join removal by editing the query's jointree.
analyzejoins.c decided which joins could be dropped by consulting the planner's derived data structures, but then implemented the removal by updating those structures in-place. That is a lot of fiddly work, and nothing keeps it in step with the rest of the planner: remove_leftjoinrel_from_query only bothered to update "parts of the planner's data structures that will actually be consulted later", with no good way to know what those are. Bug #19560 is one consequence. In that report, removing a join leaves an EquivalenceClass that now gives rise to a base restriction clause, but base restriction clauses have already been generated and nothing reconsiders them, so the WHERE condition disappears from the plan and we return wrong answers. The self-join elimination code has the same design and the same type of hazard. We have seen many related bugs over the years too, so it's time to do something drastic. To fix, do the removals by editing root->parse->jointree (which is a far simpler and more stable representation than the derived data), and then have query_planner() discard everything it computed from the jointree and derive it over again. This requires quite a bit less code, and doesn't require touching analyzejoins.c every time we change the data derived by query_planner(). For typical cases it can actually save a bit of planning time, though in cases where we have to iterate the derivation loop many times it does add some time. reduce_unique_semijoins() gets the same treatment: rather than deleting the semijoin's SpecialJoinInfo and relying on the jointree not being consulted again, it now changes the JoinExpr's jointype to JOIN_INNER and recalculates everything. Some plans change in the join regression test. Qual evaluation order shifts in a few cases, because the conditions now reach later planning in jointree order rather than in whatever order the removal code re-distributed them. A few plans improve, since the rebuilt relation targetlists no longer carry columns that only a removed join needed. We also detect a constant-false filter condition whose test used to carry a FIXME label. One plan gets marginally worse, because the old code recomputed attr_needed from equivalence classes after a join removal; that is more accurate than what deconstruct_jointree() derives from the original clauses, but we no longer do that. Making that recomputation happen anyway could be worth doing, but it should be considered independently and perhaps implemented differently. Back-patch to v16, on the grounds that the introduction of varnullingrels in v16 made the old approach significantly more complex and bug-prone; notably, bug #19560 does not manifest before v16. In released branches, do not remove externally-visible fixup functions such as remove_join_clause_from_rels, in case any extensions are relying on them; but they're no longer used by core code. But we must nonetheless break API/ABI for remove_useless_joins, reduce_unique_semijoins, and remove_useless_self_joins, as those now have different outputs and very different behavior than before. It seems unlikely that any extensions are calling those; but just in case, make the breakage more obvious by renaming remove_useless_joins to remove_useless_outer_joins, which is a more sensible name for it anyway since the addition of remove_useless_self_joins. Full disclosure: initial drafts of this patch were made with Claude Opus 4.8. Bug: #19560 Reported-by: Orestis Markou <orestis@orestis.gr> Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Richard Guo <guofenglinux@gmail.com> Reviewed-by: Thom Brown <thom@linux.com> Reviewed-by: Jacob Brazeal <jacob.brazeal@gmail.com> Discussion: https://postgr.es/m/1186816.1784573544@sss.pgh.pa.us Backpatch-through: 16
This commit is contained in:
@@ -89,6 +89,7 @@ static void ec_build_derives_hash(PlannerInfo *root, EquivalenceClass *ec);
|
||||
static void ec_add_derived_clauses(EquivalenceClass *ec, List *clauses);
|
||||
static void ec_add_derived_clause(EquivalenceClass *ec, RestrictInfo *clause);
|
||||
static void ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo);
|
||||
static void ec_clear_derived_clauses(EquivalenceClass *ec);
|
||||
static RestrictInfo *ec_search_clause_for_ems(PlannerInfo *root, EquivalenceClass *ec,
|
||||
EquivalenceMember *leftem,
|
||||
EquivalenceMember *rightem,
|
||||
@@ -1451,8 +1452,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root,
|
||||
* For the moment we force all the Vars to be available at all join nodes
|
||||
* for this eclass. Perhaps this could be improved by doing some
|
||||
* pre-analysis of which members we prefer to join, but it's no worse than
|
||||
* what happened in the pre-8.3 code. (Note: rebuild_eclass_attr_needed
|
||||
* needs to match this code.)
|
||||
* what happened in the pre-8.3 code.
|
||||
*/
|
||||
foreach(lc, ec->ec_members)
|
||||
{
|
||||
@@ -2559,51 +2559,6 @@ reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
|
||||
return false; /* failed to make any deduction */
|
||||
}
|
||||
|
||||
/*
|
||||
* rebuild_eclass_attr_needed
|
||||
* Put back attr_needed bits for Vars/PHVs needed for join eclasses.
|
||||
*
|
||||
* This is used to rebuild attr_needed/ph_needed sets after removal of a
|
||||
* useless outer join. It should match what
|
||||
* generate_base_implied_equalities_no_const did, except that we call
|
||||
* add_vars_to_attr_needed not add_vars_to_targetlist.
|
||||
*/
|
||||
void
|
||||
rebuild_eclass_attr_needed(PlannerInfo *root)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, root->eq_classes)
|
||||
{
|
||||
EquivalenceClass *ec = (EquivalenceClass *) lfirst(lc);
|
||||
|
||||
/*
|
||||
* We don't expect any EC child members to exist at this point. Ensure
|
||||
* that's the case, otherwise, we might be getting asked to do
|
||||
* something this function hasn't been coded for.
|
||||
*/
|
||||
Assert(ec->ec_childmembers == NULL);
|
||||
|
||||
/* Need do anything only for a multi-member, no-const EC. */
|
||||
if (list_length(ec->ec_members) > 1 && !ec->ec_has_const)
|
||||
{
|
||||
ListCell *lc2;
|
||||
|
||||
foreach(lc2, ec->ec_members)
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
List *vars = pull_var_clause((Node *) cur_em->em_expr,
|
||||
PVC_RECURSE_AGGREGATES |
|
||||
PVC_RECURSE_WINDOWFUNCS |
|
||||
PVC_INCLUDE_PLACEHOLDERS);
|
||||
|
||||
add_vars_to_attr_needed(root, vars, ec->ec_relids);
|
||||
list_free(vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* find_join_domain
|
||||
* Find the highest JoinDomain enclosed within the given relid set.
|
||||
@@ -3825,7 +3780,7 @@ ec_add_clause_to_derives_hash(EquivalenceClass *ec, RestrictInfo *rinfo)
|
||||
* when thousands of partitions are involved, so we free it as well -- even
|
||||
* though we do not typically free lists.
|
||||
*/
|
||||
void
|
||||
static void
|
||||
ec_clear_derived_clauses(EquivalenceClass *ec)
|
||||
{
|
||||
list_free(ec->ec_derives_list);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -295,8 +295,6 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
|
||||
* have a single owning relation; we keep their attr_needed info in
|
||||
* root->placeholder_list instead. Find or create the associated
|
||||
* PlaceHolderInfo entry, and update its ph_needed.
|
||||
*
|
||||
* See also add_vars_to_attr_needed.
|
||||
*/
|
||||
void
|
||||
add_vars_to_targetlist(PlannerInfo *root, List *vars,
|
||||
@@ -350,63 +348,6 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* add_vars_to_attr_needed
|
||||
* This does a subset of what add_vars_to_targetlist does: it just
|
||||
* updates attr_needed for Vars and ph_needed for PlaceHolderVars.
|
||||
* We assume the Vars are already in their relations' targetlists.
|
||||
*
|
||||
* This is used to rebuild attr_needed/ph_needed sets after removal
|
||||
* of a useless outer join. The removed join clause might have been
|
||||
* the only upper-level use of some other relation's Var, in which
|
||||
* case we can reduce that Var's attr_needed and thereby possibly
|
||||
* open the door to further join removals. But we can't tell that
|
||||
* without tedious reconstruction of the attr_needed data.
|
||||
*
|
||||
* Note that if a Var's attr_needed is successfully reduced to empty,
|
||||
* it will still be in the relation's targetlist even though we do
|
||||
* not really need the scan plan node to emit it. The extra plan
|
||||
* inefficiency seems tiny enough to not be worth spending planner
|
||||
* cycles to get rid of it.
|
||||
*/
|
||||
void
|
||||
add_vars_to_attr_needed(PlannerInfo *root, List *vars,
|
||||
Relids where_needed)
|
||||
{
|
||||
ListCell *temp;
|
||||
|
||||
Assert(!bms_is_empty(where_needed));
|
||||
|
||||
foreach(temp, vars)
|
||||
{
|
||||
Node *node = (Node *) lfirst(temp);
|
||||
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
RelOptInfo *rel = find_base_rel(root, var->varno);
|
||||
int attno = var->varattno;
|
||||
|
||||
if (bms_is_subset(where_needed, rel->relids))
|
||||
continue;
|
||||
Assert(attno >= rel->min_attr && attno <= rel->max_attr);
|
||||
attno -= rel->min_attr;
|
||||
rel->attr_needed[attno] = bms_add_members(rel->attr_needed[attno],
|
||||
where_needed);
|
||||
}
|
||||
else if (IsA(node, PlaceHolderVar))
|
||||
{
|
||||
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
||||
PlaceHolderInfo *phinfo = find_placeholder_info(root, phv);
|
||||
|
||||
phinfo->ph_needed = bms_add_members(phinfo->ph_needed,
|
||||
where_needed);
|
||||
}
|
||||
else
|
||||
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* GROUP BY
|
||||
@@ -1226,54 +1167,10 @@ extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex)
|
||||
*/
|
||||
add_vars_to_targetlist(root, newvars, where_needed);
|
||||
|
||||
/*
|
||||
* Remember the lateral references for rebuild_lateral_attr_needed and
|
||||
* create_lateral_join_info.
|
||||
*/
|
||||
/* Remember the lateral references for create_lateral_join_info */
|
||||
brel->lateral_vars = newvars;
|
||||
}
|
||||
|
||||
/*
|
||||
* rebuild_lateral_attr_needed
|
||||
* Put back attr_needed bits for Vars/PHVs needed for lateral references.
|
||||
*
|
||||
* This is used to rebuild attr_needed/ph_needed sets after removal of a
|
||||
* useless outer join. It should match what find_lateral_references did,
|
||||
* except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
|
||||
*/
|
||||
void
|
||||
rebuild_lateral_attr_needed(PlannerInfo *root)
|
||||
{
|
||||
Index rti;
|
||||
|
||||
/* We need do nothing if the query contains no LATERAL RTEs */
|
||||
if (!root->hasLateralRTEs)
|
||||
return;
|
||||
|
||||
/* Examine the same baserels that find_lateral_references did */
|
||||
for (rti = 1; rti < root->simple_rel_array_size; rti++)
|
||||
{
|
||||
RelOptInfo *brel = root->simple_rel_array[rti];
|
||||
Relids where_needed;
|
||||
|
||||
if (brel == NULL)
|
||||
continue;
|
||||
if (brel->reloptkind != RELOPT_BASEREL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* We don't need to repeat all of extract_lateral_references, since it
|
||||
* kindly saved the extracted Vars/PHVs in lateral_vars.
|
||||
*/
|
||||
if (brel->lateral_vars == NIL)
|
||||
continue;
|
||||
|
||||
where_needed = bms_make_singleton(rti);
|
||||
|
||||
add_vars_to_attr_needed(root, brel->lateral_vars, where_needed);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* create_lateral_join_info
|
||||
* Fill in the per-base-relation direct_lateral_relids, lateral_relids
|
||||
@@ -3227,9 +3124,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
|
||||
* var propagation is ensured by making ojscope include input rels from
|
||||
* both sides of the join.
|
||||
*
|
||||
* See also rebuild_joinclause_attr_needed, which has to partially repeat
|
||||
* this work after removal of an outer join.
|
||||
*
|
||||
* Note: if the clause gets absorbed into an EquivalenceClass then this
|
||||
* may be unnecessary, but for now we have to do it to cover the case
|
||||
* where the EC becomes ec_broken and we end up reinserting the original
|
||||
@@ -3803,11 +3697,6 @@ process_implied_equality(PlannerInfo *root,
|
||||
* some of the Vars could have missed having that done because they only
|
||||
* appeared in single-relation clauses originally. So do it here for
|
||||
* safety.
|
||||
*
|
||||
* See also rebuild_joinclause_attr_needed, which has to partially repeat
|
||||
* this work after removal of an outer join. (Since we will put this
|
||||
* clause into the joininfo lists, that function needn't do any extra work
|
||||
* to find it.)
|
||||
*/
|
||||
if (bms_membership(relids) == BMS_MULTIPLE)
|
||||
{
|
||||
@@ -3949,72 +3838,6 @@ get_join_domain_min_rels(PlannerInfo *root, Relids domain_relids)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* rebuild_joinclause_attr_needed
|
||||
* Put back attr_needed bits for Vars/PHVs needed for join clauses.
|
||||
*
|
||||
* This is used to rebuild attr_needed/ph_needed sets after removal of a
|
||||
* useless outer join. It should match what distribute_qual_to_rels did,
|
||||
* except that we call add_vars_to_attr_needed not add_vars_to_targetlist.
|
||||
*/
|
||||
void
|
||||
rebuild_joinclause_attr_needed(PlannerInfo *root)
|
||||
{
|
||||
/*
|
||||
* We must examine all join clauses, but there's no value in processing
|
||||
* any join clause more than once. So it's slightly annoying that we have
|
||||
* to find them via the per-base-relation joininfo lists. Avoid duplicate
|
||||
* processing by tracking the rinfo_serial numbers of join clauses we've
|
||||
* already seen. (This doesn't work for is_clone clauses, so we must
|
||||
* waste effort on them.)
|
||||
*/
|
||||
Bitmapset *seen_serials = NULL;
|
||||
Index rti;
|
||||
|
||||
/* Scan all baserels for join clauses */
|
||||
for (rti = 1; rti < root->simple_rel_array_size; rti++)
|
||||
{
|
||||
RelOptInfo *brel = root->simple_rel_array[rti];
|
||||
ListCell *lc;
|
||||
|
||||
if (brel == NULL)
|
||||
continue;
|
||||
if (brel->reloptkind != RELOPT_BASEREL)
|
||||
continue;
|
||||
|
||||
foreach(lc, brel->joininfo)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
|
||||
Relids relids = rinfo->required_relids;
|
||||
|
||||
if (!rinfo->is_clone) /* else serial number is not unique */
|
||||
{
|
||||
if (bms_is_member(rinfo->rinfo_serial, seen_serials))
|
||||
continue; /* saw it already */
|
||||
seen_serials = bms_add_member(seen_serials,
|
||||
rinfo->rinfo_serial);
|
||||
}
|
||||
|
||||
if (bms_membership(relids) == BMS_MULTIPLE)
|
||||
{
|
||||
List *vars = pull_var_clause((Node *) rinfo->clause,
|
||||
PVC_RECURSE_AGGREGATES |
|
||||
PVC_RECURSE_WINDOWFUNCS |
|
||||
PVC_INCLUDE_PLACEHOLDERS);
|
||||
Relids where_needed;
|
||||
|
||||
if (rinfo->is_clone)
|
||||
where_needed = bms_intersect(relids, root->all_baserels);
|
||||
else
|
||||
where_needed = relids;
|
||||
add_vars_to_attr_needed(root, vars, where_needed);
|
||||
list_free(vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* match_foreign_keys_to_quals
|
||||
* Match foreign-key constraints to equivalence classes and join quals
|
||||
@@ -4044,9 +3867,9 @@ match_foreign_keys_to_quals(PlannerInfo *root)
|
||||
|
||||
/*
|
||||
* Either relid might identify a rel that is in the query's rtable but
|
||||
* isn't referenced by the jointree, or has been removed by join
|
||||
* removal, so that it won't have a RelOptInfo. Hence don't use
|
||||
* find_base_rel() here. We can ignore such FKs.
|
||||
* isn't referenced by the jointree (typically because it's been
|
||||
* removed by join removal), so that it won't have a RelOptInfo. Hence
|
||||
* don't use find_base_rel() here. We can ignore such FKs.
|
||||
*/
|
||||
if (fkinfo->con_relid >= root->simple_rel_array_size ||
|
||||
fkinfo->ref_relid >= root->simple_rel_array_size)
|
||||
|
||||
@@ -54,33 +54,71 @@ RelOptInfo *
|
||||
query_planner(PlannerInfo *root,
|
||||
query_pathkeys_callback qp_callback, void *qp_extra)
|
||||
{
|
||||
Query *parse = root->parse;
|
||||
Query *parse;
|
||||
List *joinlist;
|
||||
RelOptInfo *final_rel;
|
||||
|
||||
/*
|
||||
* Init planner lists to empty.
|
||||
* The join simplification steps below work by modifying parse->jointree,
|
||||
* and they make no attempt to update the information we derive from it.
|
||||
* So whenever one of them succeeds, we must throw away all that derived
|
||||
* information and recompute it from scratch, which we do by looping back
|
||||
* to "restart". We cannot loop indefinitely, because each successful
|
||||
* simplification either deletes a base relation from the jointree or
|
||||
* turns a semijoin into an inner join, and neither of those can be undone
|
||||
* by a later pass.
|
||||
*
|
||||
* NOTE: append_rel_list was set up by subquery_planner, so do not touch
|
||||
* here.
|
||||
* These initial Asserts check that the state at entry is not too complex
|
||||
* for the code below to restore. There mustn't be any EquivalenceClasses
|
||||
* yet, and we should have only the top-level JoinDomain.
|
||||
*/
|
||||
Assert(root->eq_classes == NIL);
|
||||
Assert(list_length(root->join_domains) == 1);
|
||||
|
||||
restart:
|
||||
parse = root->parse;
|
||||
|
||||
/*
|
||||
* Initialize information derived from the jointree to empty.
|
||||
*
|
||||
* It's critical that this reset every field that the steps below will
|
||||
* fill in, since we may be going around this loop more than once.
|
||||
*
|
||||
* NOTE: append_rel_list was created earlier, so do not clear it here;
|
||||
* rowMarks ditto. Join simplification must update those if necessary.
|
||||
*/
|
||||
root->all_baserels = NULL;
|
||||
root->outer_join_rels = NULL;
|
||||
root->all_query_rels = NULL;
|
||||
root->join_rel_list = NIL;
|
||||
root->join_rel_hash = NULL;
|
||||
root->join_rel_level = NULL;
|
||||
root->join_cur_level = 0;
|
||||
root->eq_classes = NIL;
|
||||
root->ec_merging_done = false;
|
||||
root->canon_pathkeys = NIL;
|
||||
root->left_join_clauses = NIL;
|
||||
root->right_join_clauses = NIL;
|
||||
root->full_join_clauses = NIL;
|
||||
root->join_info_list = NIL;
|
||||
root->last_rinfo_serial = 0;
|
||||
root->placeholder_list = NIL;
|
||||
root->placeholder_array = NULL;
|
||||
root->placeholder_array_size = 0;
|
||||
root->placeholdersFrozen = false;
|
||||
root->agg_clause_list = NIL;
|
||||
root->group_expr_list = NIL;
|
||||
root->tlist_vars = NIL;
|
||||
root->fkey_list = NIL;
|
||||
root->initial_rels = NIL;
|
||||
root->hasPseudoConstantQuals = false;
|
||||
|
||||
/*
|
||||
* We don't want to delete the top-level join domain, but get rid of other
|
||||
* ones so as to reset the list to initial state. deconstruct_jointree
|
||||
* will take care of (re)computing the top level's jd_relids.
|
||||
*/
|
||||
root->join_domains = list_truncate(root->join_domains, 1);
|
||||
|
||||
/*
|
||||
* Set up arrays for accessing base relations and AppendRelInfos.
|
||||
@@ -144,6 +182,21 @@ query_planner(PlannerInfo *root,
|
||||
/* Select cheapest path (pretty easy in this case...) */
|
||||
set_cheapest(final_rel);
|
||||
|
||||
/*
|
||||
* Fill in all_result_relids and leaf_result_relids, just in
|
||||
* case something looks at them (at this writing, the core
|
||||
* code won't). This must match the similar stanza below.
|
||||
*/
|
||||
if (parse->resultRelation)
|
||||
{
|
||||
int rti = parse->resultRelation;
|
||||
RangeTblEntry *res_rte = root->simple_rte_array[rti];
|
||||
|
||||
root->all_result_relids = bms_make_singleton(rti);
|
||||
if (!res_rte->inh)
|
||||
root->leaf_result_relids = bms_make_singleton(rti);
|
||||
}
|
||||
|
||||
/*
|
||||
* We don't need to run generate_base_implied_equalities, but
|
||||
* we do need to pretend that EC merging is complete.
|
||||
@@ -227,19 +280,30 @@ query_planner(PlannerInfo *root,
|
||||
* Remove any useless outer joins. Ideally this would be done during
|
||||
* jointree preprocessing, but the necessary information isn't available
|
||||
* until we've built baserel data structures and classified qual clauses.
|
||||
* If we remove a join, loop back to the top and redo what we did so far.
|
||||
*/
|
||||
joinlist = remove_useless_joins(root, joinlist);
|
||||
if (remove_useless_outer_joins(root))
|
||||
goto restart;
|
||||
|
||||
/*
|
||||
* Also, reduce any semijoins with unique inner rels to plain inner joins.
|
||||
* Likewise, this can't be done until now for lack of needed info.
|
||||
* Likewise, this can't be done until now for lack of needed info, and we
|
||||
* must loop around if we find any simplifications.
|
||||
*/
|
||||
reduce_unique_semijoins(root);
|
||||
if (reduce_unique_semijoins(root))
|
||||
goto restart;
|
||||
|
||||
/*
|
||||
* Remove self joins on a unique column.
|
||||
* Remove self joins on a unique column. Again, this couldn't be done any
|
||||
* earlier, and we must loop around if we find anything to remove.
|
||||
*/
|
||||
if (remove_useless_self_joins(root, joinlist))
|
||||
goto restart;
|
||||
|
||||
/*
|
||||
* No more join simplifications apply, so we're done looping. Code below
|
||||
* this point does not need to be able to restart.
|
||||
*/
|
||||
joinlist = remove_useless_self_joins(root, joinlist);
|
||||
|
||||
/*
|
||||
* Now distribute "placeholders" to base rels as needed. This has to be
|
||||
@@ -274,6 +338,22 @@ query_planner(PlannerInfo *root,
|
||||
*/
|
||||
setup_eager_aggregation(root);
|
||||
|
||||
/*
|
||||
* If there's a result relation, initialize all_result_relids to include
|
||||
* it; and if we've verified that it is non-inheriting, mark it as a leaf
|
||||
* target. add_other_rels_to_query() will expand these sets if the result
|
||||
* relation has children.
|
||||
*/
|
||||
if (parse->resultRelation)
|
||||
{
|
||||
int rti = parse->resultRelation;
|
||||
RangeTblEntry *rte = root->simple_rte_array[rti];
|
||||
|
||||
root->all_result_relids = bms_make_singleton(rti);
|
||||
if (!rte->inh)
|
||||
root->leaf_result_relids = bms_make_singleton(rti);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now expand appendrels by adding "otherrels" for their children. We
|
||||
* delay this to the end so that we have as much information as possible
|
||||
|
||||
@@ -803,9 +803,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
|
||||
root->eq_classes = NIL;
|
||||
root->ec_merging_done = false;
|
||||
root->last_rinfo_serial = 0;
|
||||
root->all_result_relids =
|
||||
parse->resultRelation ? bms_make_singleton(parse->resultRelation) : NULL;
|
||||
root->leaf_result_relids = NULL; /* we'll find out leaf-ness later */
|
||||
root->all_result_relids = NULL;
|
||||
root->leaf_result_relids = NULL;
|
||||
root->append_rel_list = NIL;
|
||||
root->row_identity_vars = NIL;
|
||||
root->rowMarks = NIL;
|
||||
@@ -978,19 +977,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
|
||||
list_length(rte->securityQuals));
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have now verified that the query target relation is
|
||||
* non-inheriting, mark it as a leaf target.
|
||||
*/
|
||||
if (parse->resultRelation)
|
||||
{
|
||||
RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
|
||||
|
||||
if (!rte->inh)
|
||||
root->leaf_result_relids =
|
||||
bms_make_singleton(parse->resultRelation);
|
||||
}
|
||||
|
||||
/*
|
||||
* This would be a convenient time to check access permissions for all
|
||||
* relations mentioned in the query, since it would be better to fail now,
|
||||
|
||||
@@ -145,39 +145,3 @@ add_join_clause_to_rels(PlannerInfo *root,
|
||||
rel->joininfo = lappend(rel->joininfo, restrictinfo);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* remove_join_clause_from_rels
|
||||
* Delete 'restrictinfo' from all the joininfo lists it is in
|
||||
*
|
||||
* This reverses the effect of add_join_clause_to_rels. It's used when we
|
||||
* discover that a relation need not be joined at all.
|
||||
*
|
||||
* 'restrictinfo' describes the join clause
|
||||
* 'join_relids' is the set of relations participating in the join clause
|
||||
* (some of these could be outer joins)
|
||||
*/
|
||||
void
|
||||
remove_join_clause_from_rels(PlannerInfo *root,
|
||||
RestrictInfo *restrictinfo,
|
||||
Relids join_relids)
|
||||
{
|
||||
int cur_relid;
|
||||
|
||||
cur_relid = -1;
|
||||
while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
|
||||
{
|
||||
RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid);
|
||||
|
||||
/* We would only have added the clause to baserels */
|
||||
if (rel == NULL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Remove the restrictinfo from the list. Pointer comparison is
|
||||
* sufficient.
|
||||
*/
|
||||
Assert(list_member_ptr(rel->joininfo, restrictinfo));
|
||||
rel->joininfo = list_delete_ptr(rel->joininfo, restrictinfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,33 +316,6 @@ fix_placeholder_input_needed_levels(PlannerInfo *root)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* rebuild_placeholder_attr_needed
|
||||
* Put back attr_needed bits for Vars/PHVs needed in PlaceHolderVars.
|
||||
*
|
||||
* This is used to rebuild attr_needed/ph_needed sets after removal of a
|
||||
* useless outer join. It should match what
|
||||
* fix_placeholder_input_needed_levels did, except that we call
|
||||
* add_vars_to_attr_needed not add_vars_to_targetlist.
|
||||
*/
|
||||
void
|
||||
rebuild_placeholder_attr_needed(PlannerInfo *root)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
foreach(lc, root->placeholder_list)
|
||||
{
|
||||
PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
|
||||
List *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
|
||||
PVC_RECURSE_AGGREGATES |
|
||||
PVC_RECURSE_WINDOWFUNCS |
|
||||
PVC_INCLUDE_PLACEHOLDERS);
|
||||
|
||||
add_vars_to_attr_needed(root, vars, phinfo->ph_eval_at);
|
||||
list_free(vars);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* add_placeholders_to_base_rels
|
||||
* Add any required PlaceHolderVars to base rels' targetlists.
|
||||
|
||||
@@ -64,6 +64,7 @@ static bool contain_windowfuncs_walker(Node *node, void *context);
|
||||
static bool locate_windowfunc_walker(Node *node,
|
||||
locate_windowfunc_context *context);
|
||||
static bool checkExprHasSubLink_walker(Node *node, void *context);
|
||||
static Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
|
||||
static Node *add_nulling_relids_mutator(Node *node,
|
||||
add_nulling_relids_context *context);
|
||||
static Node *remove_nulling_relids_mutator(Node *node,
|
||||
@@ -528,23 +529,32 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up)
|
||||
*
|
||||
* Find all Var nodes in the given tree belonging to a specific relation
|
||||
* (identified by sublevels_up and rt_index), and change their varno fields
|
||||
* to 'new_index'. The varnosyn fields are changed too. Also, adjust other
|
||||
* nodes that contain rangetable indexes, such as RangeTblRef and JoinExpr.
|
||||
* to 'new_index', and update varnosyn and varnullingrels fields similarly.
|
||||
* Also adjust other nodes that contain rangetable indexes, such as
|
||||
* RangeTblRef and JoinExpr.
|
||||
*
|
||||
* Also, new_index can be INVALID_VAR to indicate that we are deleting the
|
||||
* given relid from the tree. In this case we expect to find rt_index only
|
||||
* in Relids fields (varnullingrels, phnullingrels, phrels), never in any
|
||||
* field that identifies a single relation.
|
||||
*
|
||||
* 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!
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int rt_index;
|
||||
int new_index;
|
||||
int sublevels_up;
|
||||
} ChangeVarNodes_context;
|
||||
|
||||
static bool
|
||||
ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
if (context->callback && context->callback(node, context))
|
||||
return false;
|
||||
|
||||
if (IsA(node, Var))
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
@@ -552,12 +562,18 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
if (var->varlevelsup == context->sublevels_up)
|
||||
{
|
||||
if (var->varno == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
var->varno = context->new_index;
|
||||
}
|
||||
var->varnullingrels = adjust_relid_set(var->varnullingrels,
|
||||
context->rt_index,
|
||||
context->new_index);
|
||||
if (var->varnosyn == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
var->varnosyn = context->new_index;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -567,7 +583,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
|
||||
if (context->sublevels_up == 0 &&
|
||||
cexpr->cvarno == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
cexpr->cvarno = context->new_index;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (IsA(node, RangeTblRef))
|
||||
@@ -576,7 +595,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
|
||||
if (context->sublevels_up == 0 &&
|
||||
rtr->rtindex == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
rtr->rtindex = context->new_index;
|
||||
}
|
||||
/* the subquery itself is visited separately */
|
||||
return false;
|
||||
}
|
||||
@@ -586,7 +608,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
|
||||
if (context->sublevels_up == 0 &&
|
||||
j->rtindex == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
j->rtindex = context->new_index;
|
||||
}
|
||||
/* fall through to examine children */
|
||||
}
|
||||
if (IsA(node, PlaceHolderVar))
|
||||
@@ -611,9 +636,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
if (context->sublevels_up == 0)
|
||||
{
|
||||
if (rowmark->rti == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
rowmark->rti = context->new_index;
|
||||
}
|
||||
if (rowmark->prti == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
rowmark->prti = context->new_index;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -624,9 +655,15 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
if (context->sublevels_up == 0)
|
||||
{
|
||||
if (appinfo->parent_relid == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
appinfo->parent_relid = context->new_index;
|
||||
}
|
||||
if (appinfo->child_relid == context->rt_index)
|
||||
{
|
||||
Assert(context->new_index != INVALID_VAR);
|
||||
appinfo->child_relid = context->new_index;
|
||||
}
|
||||
}
|
||||
/* fall through to examine children */
|
||||
}
|
||||
@@ -649,28 +686,14 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
|
||||
return expression_tree_walker(node, ChangeVarNodes_walker, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* ChangeVarNodesExtended - similar to ChangeVarNodes, but with an additional
|
||||
* 'callback' param
|
||||
*
|
||||
* ChangeVarNodes changes a given node and all of its underlying nodes. This
|
||||
* version of function additionally takes a callback, which has a chance to
|
||||
* process a node before ChangeVarNodes_walker. A callback returns a boolean
|
||||
* value indicating if the given node should be skipped from further processing
|
||||
* by ChangeVarNodes_walker. The callback is called only for expressions and
|
||||
* other children nodes of a Query processed by a walker. Initial processing
|
||||
* of the root Query node doesn't invoke the callback.
|
||||
*/
|
||||
void
|
||||
ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
|
||||
int sublevels_up, ChangeVarNodes_callback callback)
|
||||
ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
|
||||
{
|
||||
ChangeVarNodes_context context;
|
||||
|
||||
context.rt_index = rt_index;
|
||||
context.new_index = new_index;
|
||||
context.sublevels_up = sublevels_up;
|
||||
context.callback = callback;
|
||||
|
||||
/*
|
||||
* Must be prepared to start with a Query or a bare expression tree; if
|
||||
@@ -694,21 +717,33 @@ ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
|
||||
ListCell *l;
|
||||
|
||||
if (qry->resultRelation == rt_index)
|
||||
{
|
||||
Assert(new_index != INVALID_VAR);
|
||||
qry->resultRelation = new_index;
|
||||
}
|
||||
|
||||
if (qry->mergeTargetRelation == rt_index)
|
||||
{
|
||||
Assert(new_index != INVALID_VAR);
|
||||
qry->mergeTargetRelation = new_index;
|
||||
}
|
||||
|
||||
/* this is unlikely to ever be used, but ... */
|
||||
if (qry->onConflict && qry->onConflict->exclRelIndex == rt_index)
|
||||
{
|
||||
Assert(new_index != INVALID_VAR);
|
||||
qry->onConflict->exclRelIndex = new_index;
|
||||
}
|
||||
|
||||
foreach(l, qry->rowMarks)
|
||||
{
|
||||
RowMarkClause *rc = (RowMarkClause *) lfirst(l);
|
||||
|
||||
if (rc->rti == rt_index)
|
||||
{
|
||||
Assert(new_index != INVALID_VAR);
|
||||
rc->rti = new_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
query_tree_walker(qry, ChangeVarNodes_walker, &context, 0);
|
||||
@@ -717,36 +752,6 @@ ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
|
||||
ChangeVarNodes_walker(node, &context);
|
||||
}
|
||||
|
||||
void
|
||||
ChangeVarNodes(Node *node, int rt_index, int new_index, int sublevels_up)
|
||||
{
|
||||
ChangeVarNodesExtended(node, rt_index, new_index, sublevels_up, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* ChangeVarNodesWalkExpression - process subexpression within a callback
|
||||
* function passed to ChangeVarNodesExtended.
|
||||
*
|
||||
* This is intended to be used by a callback that needs to recursively
|
||||
* process subexpressions of some node being visited by an outer
|
||||
* ChangeVarNodesExtended call, instead of relying on ChangeVarNodes_walker's
|
||||
* default recursion. We invoke ChangeVarNodes_walker directly rather than
|
||||
* via expression_tree_walker, because expression_tree_walker only visits
|
||||
* child nodes and would fail to process the passed node itself --
|
||||
* for example, a bare Var node would not get its varno adjusted.
|
||||
*
|
||||
* Because this calls ChangeVarNodes_walker directly, if the passed node is
|
||||
* a Query, it will be treated as a sub-Query: sublevels_up is incremented
|
||||
* before recursing into it, and Query-level fields (resultRelation,
|
||||
* mergeTargetRelation, rowMarks, etc.) will not be adjusted. Do not apply
|
||||
* this to a top-level Query node; use ChangeVarNodesExtended for that.
|
||||
*/
|
||||
bool
|
||||
ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context)
|
||||
{
|
||||
return ChangeVarNodes_walker(node, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* adjust_relid_set - substitute newrelid for oldrelid in a Relid set
|
||||
*
|
||||
@@ -756,7 +761,7 @@ ChangeVarNodesWalkExpression(Node *node, ChangeVarNodes_context *context)
|
||||
* a special varno, this function does nothing. When newrelid is a special
|
||||
* varno, this function behaves as delete.
|
||||
*/
|
||||
Relids
|
||||
static Relids
|
||||
adjust_relid_set(Relids relids, int oldrelid, int newrelid)
|
||||
{
|
||||
if (!IS_SPECIAL_VARNO(oldrelid) && bms_is_member(oldrelid, relids))
|
||||
|
||||
@@ -217,6 +217,9 @@ typedef struct Expr
|
||||
* row identity information during UPDATE/DELETE/MERGE. This value should
|
||||
* never be seen outside the planner.
|
||||
*
|
||||
* INVALID_VAR should never appear as anything's varno. We use it in a
|
||||
* few APIs to denote removal of an RTE.
|
||||
*
|
||||
* varnullingrels is the set of RT indexes of outer joins that can force
|
||||
* the Var's value to null (at the point where it appears in the query).
|
||||
* See optimizer/README for discussion of that.
|
||||
@@ -244,6 +247,7 @@ typedef struct Expr
|
||||
#define OUTER_VAR (-2) /* reference to outer subplan */
|
||||
#define INDEX_VAR (-3) /* reference to index column */
|
||||
#define ROWID_VAR (-4) /* row identity column during planning */
|
||||
#define INVALID_VAR (-5) /* this is not a valid varno! */
|
||||
|
||||
#define IS_SPECIAL_VARNO(varno) ((int) (varno) < 0)
|
||||
|
||||
|
||||
@@ -23,8 +23,5 @@ extern bool have_relevant_joinclause(PlannerInfo *root,
|
||||
extern void add_join_clause_to_rels(PlannerInfo *root,
|
||||
RestrictInfo *restrictinfo,
|
||||
Relids join_relids);
|
||||
extern void remove_join_clause_from_rels(PlannerInfo *root,
|
||||
RestrictInfo *restrictinfo,
|
||||
Relids join_relids);
|
||||
|
||||
#endif /* JOININFO_H */
|
||||
|
||||
@@ -137,7 +137,6 @@ extern bool process_equivalence(PlannerInfo *root,
|
||||
extern Expr *canonicalize_ec_expression(Expr *expr,
|
||||
Oid req_type, Oid req_collation);
|
||||
extern void reconsider_outer_join_clauses(PlannerInfo *root);
|
||||
extern void rebuild_eclass_attr_needed(PlannerInfo *root);
|
||||
extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root,
|
||||
Expr *expr,
|
||||
List *opfamilies,
|
||||
@@ -208,7 +207,6 @@ extern bool eclass_useful_for_merging(PlannerInfo *root,
|
||||
extern bool is_redundant_derived_clause(RestrictInfo *rinfo, List *clauselist);
|
||||
extern bool is_redundant_with_indexclauses(RestrictInfo *rinfo,
|
||||
List *indexclauses);
|
||||
extern void ec_clear_derived_clauses(EquivalenceClass *ec);
|
||||
|
||||
/*
|
||||
* pathkeys.c
|
||||
|
||||
@@ -23,7 +23,6 @@ extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
|
||||
PlaceHolderVar *phv);
|
||||
extern void find_placeholders_in_jointree(PlannerInfo *root);
|
||||
extern void fix_placeholder_input_needed_levels(PlannerInfo *root);
|
||||
extern void rebuild_placeholder_attr_needed(PlannerInfo *root);
|
||||
extern void add_placeholders_to_base_rels(PlannerInfo *root);
|
||||
extern void add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
|
||||
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
|
||||
|
||||
@@ -73,12 +73,9 @@ extern void add_other_rels_to_query(PlannerInfo *root);
|
||||
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
|
||||
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
|
||||
Relids where_needed);
|
||||
extern void add_vars_to_attr_needed(PlannerInfo *root, List *vars,
|
||||
Relids where_needed);
|
||||
extern void remove_useless_groupby_columns(PlannerInfo *root);
|
||||
extern void setup_eager_aggregation(PlannerInfo *root);
|
||||
extern void find_lateral_references(PlannerInfo *root);
|
||||
extern void rebuild_lateral_attr_needed(PlannerInfo *root);
|
||||
extern void create_lateral_join_info(PlannerInfo *root);
|
||||
extern List *deconstruct_jointree(PlannerInfo *root);
|
||||
extern bool restriction_is_always_true(PlannerInfo *root,
|
||||
@@ -102,24 +99,19 @@ extern RestrictInfo *build_implied_join_equality(PlannerInfo *root,
|
||||
Expr *item2,
|
||||
Relids qualscope,
|
||||
Index security_level);
|
||||
extern void rebuild_joinclause_attr_needed(PlannerInfo *root);
|
||||
extern void match_foreign_keys_to_quals(PlannerInfo *root);
|
||||
|
||||
/*
|
||||
* prototypes for plan/analyzejoins.c
|
||||
*/
|
||||
extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
|
||||
extern void reduce_unique_semijoins(PlannerInfo *root);
|
||||
extern bool remove_useless_outer_joins(PlannerInfo *root);
|
||||
extern bool reduce_unique_semijoins(PlannerInfo *root);
|
||||
extern bool query_supports_distinctness(Query *query);
|
||||
extern bool query_is_distinct_for(Query *query, List *distinct_cols);
|
||||
extern bool innerrel_is_unique(PlannerInfo *root,
|
||||
Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel,
|
||||
JoinType jointype, List *restrictlist, bool force_cache);
|
||||
extern bool innerrel_is_unique_ext(PlannerInfo *root, Relids joinrelids,
|
||||
Relids outerrelids, RelOptInfo *innerrel,
|
||||
JoinType jointype, List *restrictlist,
|
||||
bool force_cache, List **extra_clauses);
|
||||
extern List *remove_useless_self_joins(PlannerInfo *root, List *joinlist);
|
||||
extern bool remove_useless_self_joins(PlannerInfo *root, List *joinlist);
|
||||
|
||||
/*
|
||||
* prototypes for plan/setrefs.c
|
||||
|
||||
@@ -41,30 +41,11 @@ typedef enum ReplaceVarsNoMatchOption
|
||||
REPLACEVARS_SUBSTITUTE_NULL, /* replace with a NULL Const */
|
||||
} ReplaceVarsNoMatchOption;
|
||||
|
||||
typedef struct ChangeVarNodes_context ChangeVarNodes_context;
|
||||
|
||||
typedef bool (*ChangeVarNodes_callback) (Node *node,
|
||||
ChangeVarNodes_context *arg);
|
||||
|
||||
struct ChangeVarNodes_context
|
||||
{
|
||||
int rt_index;
|
||||
int new_index;
|
||||
int sublevels_up;
|
||||
ChangeVarNodes_callback callback;
|
||||
};
|
||||
|
||||
pg_nodiscard extern Relids adjust_relid_set(Relids relids, int oldrelid, int newrelid);
|
||||
extern void CombineRangeTables(List **dst_rtable, List **dst_perminfos,
|
||||
List *src_rtable, List *src_perminfos);
|
||||
extern void OffsetVarNodes(Node *node, int offset, int sublevels_up);
|
||||
extern void ChangeVarNodes(Node *node, int rt_index, int new_index,
|
||||
int sublevels_up);
|
||||
extern void ChangeVarNodesExtended(Node *node, int rt_index, int new_index,
|
||||
int sublevels_up,
|
||||
ChangeVarNodes_callback callback);
|
||||
extern bool ChangeVarNodesWalkExpression(Node *node,
|
||||
ChangeVarNodes_context *context);
|
||||
extern void IncrementVarSublevelsUp(Node *node, int delta_sublevels_up,
|
||||
int min_sublevels_up);
|
||||
extern void IncrementVarSublevelsUp_rtable(List *rtable,
|
||||
|
||||
@@ -7681,6 +7681,38 @@ where t1.a = s.c;
|
||||
----------
|
||||
(0 rows)
|
||||
|
||||
rollback;
|
||||
-- join removal bug #19560: removing a join can leave an EquivalenceClass that
|
||||
-- now yields a base restriction clause, so we must redo equivalence
|
||||
-- processing from scratch
|
||||
begin;
|
||||
create temp table items (id text, owner text);
|
||||
create temp table follows (item_id text, user_id text,
|
||||
unique (user_id, item_id));
|
||||
insert into items values ('item1', 'alice');
|
||||
explain (costs off)
|
||||
with viewer as (select 'bob' as id)
|
||||
select count(*) from items
|
||||
left join follows on follows.item_id = items.id and follows.user_id = 'bob'
|
||||
left join viewer on true
|
||||
where items.owner = viewer.id;
|
||||
QUERY PLAN
|
||||
---------------------------------------
|
||||
Aggregate
|
||||
-> Seq Scan on items
|
||||
Filter: (owner = 'bob'::text)
|
||||
(3 rows)
|
||||
|
||||
with viewer as (select 'bob' as id)
|
||||
select count(*) from items
|
||||
left join follows on follows.item_id = items.id and follows.user_id = 'bob'
|
||||
left join viewer on true
|
||||
where items.owner = viewer.id;
|
||||
count
|
||||
-------
|
||||
0
|
||||
(1 row)
|
||||
|
||||
rollback;
|
||||
-- check handling of semijoins after join removal: we must suppress
|
||||
-- unique-ification of known-constant values
|
||||
@@ -7706,17 +7738,17 @@ where exists (select 1 from t t4
|
||||
Output: t1.a
|
||||
Index Cond: (t1.a = 1)
|
||||
-> HashAggregate
|
||||
Output: t5.a
|
||||
Output: t4.a, t5.a
|
||||
Group Key: t5.a
|
||||
-> Hash Join
|
||||
Output: t5.a
|
||||
Output: t4.a, t5.a
|
||||
Hash Cond: (t6.b = t4.b)
|
||||
-> Seq Scan on pg_temp.t t6
|
||||
Output: t6.a, t6.b
|
||||
-> Hash
|
||||
Output: t4.b, t5.b, t5.a
|
||||
Output: t4.b, t4.a, t5.b, t5.a
|
||||
-> Hash Join
|
||||
Output: t4.b, t5.b, t5.a
|
||||
Output: t4.b, t4.a, t5.b, t5.a
|
||||
Inner Unique: true
|
||||
Hash Cond: (t5.b = t4.b)
|
||||
-> Seq Scan on pg_temp.t t5
|
||||
@@ -8148,7 +8180,7 @@ on q1.ax = q2.a;
|
||||
Nested Loop Left Join
|
||||
Join Filter: (t2.a = t4.a)
|
||||
-> Seq Scan on sj t2
|
||||
Filter: ((b IS NULL) AND (a IS NOT NULL) AND ((c * c) = (c + 2)))
|
||||
Filter: ((a IS NOT NULL) AND (b IS NULL) AND ((c * c) = (c + 2)))
|
||||
-> Seq Scan on sj t4
|
||||
Filter: (c IS NOT NULL)
|
||||
(6 rows)
|
||||
@@ -8231,12 +8263,70 @@ select t1.a from sj t1 where t1.b in (
|
||||
-> Seq Scan on public.sj t1
|
||||
Output: t1.a, t1.b, t1.c
|
||||
-> Materialize
|
||||
Output: t3.c, t3.b
|
||||
Output: t3.b
|
||||
-> Seq Scan on public.sj t3
|
||||
Output: t3.c, t3.b
|
||||
Output: t3.b
|
||||
Filter: (t3.c IS NOT NULL)
|
||||
(10 rows)
|
||||
|
||||
-- Check that quals get hoisted to the appropriate join level after SJE removal
|
||||
explain (verbose, costs off)
|
||||
select a2.a
|
||||
from sj b1
|
||||
join sj a1 on b1.b = a1.a
|
||||
join sj a2 on a2.a = a1.a and a2.b = a1.b;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------
|
||||
Nested Loop
|
||||
Output: a2.a
|
||||
Join Filter: (b1.b = a2.a)
|
||||
-> Seq Scan on public.sj a2
|
||||
Output: a2.a, a2.b, a2.c
|
||||
Filter: ((a2.a IS NOT NULL) AND (a2.b IS NOT NULL))
|
||||
-> Seq Scan on public.sj b1
|
||||
Output: b1.a, b1.b, b1.c
|
||||
(8 rows)
|
||||
|
||||
-- Same, when a semijoin removal happens first
|
||||
explain (verbose, costs off)
|
||||
select a1.a from sj b1 join sj a1 on a1.a = b1.b
|
||||
where exists (select 1 from sj s where s.a = a1.a);
|
||||
QUERY PLAN
|
||||
-----------------------------------------
|
||||
Nested Loop
|
||||
Output: s.a
|
||||
Inner Unique: true
|
||||
Join Filter: (b1.b = s.a)
|
||||
-> Seq Scan on public.sj b1
|
||||
Output: b1.a, b1.b, b1.c
|
||||
-> Materialize
|
||||
Output: s.a
|
||||
-> Seq Scan on public.sj s
|
||||
Output: s.a
|
||||
Filter: (s.a IS NOT NULL)
|
||||
(11 rows)
|
||||
|
||||
-- A different case, where modified qual is on a lower join level
|
||||
explain (verbose, costs off)
|
||||
select a2.a
|
||||
from ((sj b1 join sj a1 on true) join sj c1 on c1.b = a1.a)
|
||||
join sj a2 on a2.a = a1.a and a2.b = a1.b;
|
||||
QUERY PLAN
|
||||
-------------------------------------------------------------------
|
||||
Nested Loop
|
||||
Output: a2.a
|
||||
-> Nested Loop
|
||||
Output: a2.a
|
||||
Join Filter: (c1.b = a2.a)
|
||||
-> Seq Scan on public.sj a2
|
||||
Output: a2.a, a2.b, a2.c
|
||||
Filter: ((a2.a IS NOT NULL) AND (a2.b IS NOT NULL))
|
||||
-> Seq Scan on public.sj c1
|
||||
Output: c1.a, c1.b, c1.c
|
||||
-> Seq Scan on public.sj b1
|
||||
Output: b1.a, b1.b, b1.c
|
||||
(12 rows)
|
||||
|
||||
--
|
||||
-- SJE corner case: uniqueness of an inner is [partially] derived from
|
||||
-- baserestrictinfo clauses.
|
||||
@@ -8567,15 +8657,15 @@ explain (costs off) select * from sj p join sj q on p.a = q.a
|
||||
-> Seq Scan on sj r
|
||||
(6 rows)
|
||||
|
||||
-- FIXME this constant false filter doesn't look good. Should we merge
|
||||
-- equivalence classes?
|
||||
-- Check that we detect constant-false condition after merging ECs.
|
||||
explain (costs off)
|
||||
select * from sj p, sj q where p.a = q.a and p.b = 1 and q.b = 2;
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------
|
||||
Seq Scan on sj q
|
||||
Filter: ((a IS NOT NULL) AND (b = 2) AND (b = 1))
|
||||
(2 rows)
|
||||
QUERY PLAN
|
||||
--------------------------
|
||||
Result
|
||||
Replaces: Scan on q
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
-- Check that attr_needed is updated correctly after self-join removal. In this
|
||||
-- test, the join of j1 with j2 is removed. k1.b is required at either j1 or j2.
|
||||
@@ -8711,11 +8801,9 @@ where s1.x = 1;
|
||||
-> Seq Scan on public.emp1 t1
|
||||
Output: t1.id, t1.code
|
||||
-> Materialize
|
||||
Output: t3.id
|
||||
-> Seq Scan on public.emp1 t3
|
||||
Output: t3.id
|
||||
Filter: (1 = 1)
|
||||
(9 rows)
|
||||
(7 rows)
|
||||
|
||||
-- Check that PHVs do not impose any constraints on removing self joins
|
||||
explain (verbose, costs off)
|
||||
@@ -8765,14 +8853,14 @@ SELECT 1 FROM tbl_phv t1 LEFT JOIN
|
||||
(SELECT y FROM tbl_phv tr) t4
|
||||
ON t4.y = t3.y
|
||||
ON true WHERE t3.extra IS NOT NULL AND t3.x = t1.x % 2;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------
|
||||
QUERY PLAN
|
||||
--------------------------------------------------------------
|
||||
Nested Loop
|
||||
Output: 1
|
||||
-> Seq Scan on public.tbl_phv t1
|
||||
Output: t1.x, t1.y
|
||||
-> Index Scan using tbl_phv_idx on public.tbl_phv tr
|
||||
Output: tr.x, tr.y
|
||||
-> Index Only Scan using tbl_phv_idx on public.tbl_phv tr
|
||||
Output: tr.x
|
||||
Index Cond: (tr.x = (t1.x % 2))
|
||||
Filter: (1 IS NOT NULL)
|
||||
(8 rows)
|
||||
@@ -8953,7 +9041,7 @@ where t1.b = t2.b and t2.a = 3 and t1.a = 3
|
||||
---------------------------------------------------------------------------------------------
|
||||
Seq Scan on public.sl t2
|
||||
Output: t2.a, t2.b, t2.c, t2.a, t2.b, t2.c
|
||||
Filter: ((t2.c IS NOT NULL) AND (t2.b IS NOT NULL) AND (t2.a IS NOT NULL) AND (t2.a = 3))
|
||||
Filter: ((t2.b IS NOT NULL) AND (t2.c IS NOT NULL) AND (t2.a IS NOT NULL) AND (t2.a = 3))
|
||||
(3 rows)
|
||||
|
||||
-- Join qual isn't mergejoinable, but inner is unique.
|
||||
@@ -8995,10 +9083,35 @@ SELECT 1 AS c1 FROM sl sl1 LEFT JOIN (sl AS sl2 NATURAL JOIN sl AS sl3)
|
||||
-> Nested Loop Left Join
|
||||
Join Filter: sl3.bool_col
|
||||
-> Seq Scan on sl sl3
|
||||
Filter: (bool_col AND (a IS NOT NULL) AND (b IS NOT NULL) AND (c IS NOT NULL) AND (bool_col IS NOT NULL))
|
||||
Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND (c IS NOT NULL) AND (bool_col IS NOT NULL) AND bool_col)
|
||||
-> Seq Scan on sl sl4
|
||||
(7 rows)
|
||||
|
||||
-- Check that quals of a jointree node that becomes empty when the self-join
|
||||
-- is removed are not lost, and that they don't migrate above an outer join
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s, sl t
|
||||
WHERE t.a = s.a AND t.b = s.b;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------
|
||||
Seq Scan on sl
|
||||
Filter: ((c IS NOT NULL) AND (a IS NOT NULL) AND (b IS NOT NULL))
|
||||
(2 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT t1.a, ss.a FROM sl t1
|
||||
LEFT JOIN (SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s
|
||||
JOIN sl t ON t.a = s.a AND t.b = s.b) ss
|
||||
ON ss.a = t1.a;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------------
|
||||
Nested Loop Left Join
|
||||
Join Filter: (sl.a = t1.a)
|
||||
-> Seq Scan on sl t1
|
||||
-> Seq Scan on sl
|
||||
Filter: ((c IS NOT NULL) AND (a IS NOT NULL) AND (b IS NOT NULL))
|
||||
(5 rows)
|
||||
|
||||
-- Check optimization disabling if it will violate special join conditions.
|
||||
-- Two identical joined relations satisfies self join removal conditions but
|
||||
-- stay in different special join infos.
|
||||
|
||||
@@ -536,6 +536,17 @@ NOTICE: f_leak => awesome science fiction
|
||||
9 | 22 | 1 | regress_rls_dave | awesome science fiction
|
||||
(4 rows)
|
||||
|
||||
-- a rel with RLS quals can still be removed by outer-join removal
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------
|
||||
Seq Scan on category c
|
||||
InitPlan expr_1
|
||||
-> Index Scan using uaccount_pkey on uaccount
|
||||
Index Cond: (pguser = CURRENT_USER)
|
||||
(4 rows)
|
||||
|
||||
-- viewpoint from regress_rls_carol
|
||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
||||
SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
|
||||
|
||||
@@ -2898,6 +2898,31 @@ where t1.a = s.c;
|
||||
|
||||
rollback;
|
||||
|
||||
-- join removal bug #19560: removing a join can leave an EquivalenceClass that
|
||||
-- now yields a base restriction clause, so we must redo equivalence
|
||||
-- processing from scratch
|
||||
begin;
|
||||
|
||||
create temp table items (id text, owner text);
|
||||
create temp table follows (item_id text, user_id text,
|
||||
unique (user_id, item_id));
|
||||
insert into items values ('item1', 'alice');
|
||||
|
||||
explain (costs off)
|
||||
with viewer as (select 'bob' as id)
|
||||
select count(*) from items
|
||||
left join follows on follows.item_id = items.id and follows.user_id = 'bob'
|
||||
left join viewer on true
|
||||
where items.owner = viewer.id;
|
||||
|
||||
with viewer as (select 'bob' as id)
|
||||
select count(*) from items
|
||||
left join follows on follows.item_id = items.id and follows.user_id = 'bob'
|
||||
left join viewer on true
|
||||
where items.owner = viewer.id;
|
||||
|
||||
rollback;
|
||||
|
||||
-- check handling of semijoins after join removal: we must suppress
|
||||
-- unique-ification of known-constant values
|
||||
begin;
|
||||
@@ -3164,6 +3189,24 @@ explain (verbose, costs off)
|
||||
select t1.a from sj t1 where t1.b in (
|
||||
select t2.b from sj t2 join sj t3 on t2.c=t3.c);
|
||||
|
||||
-- Check that quals get hoisted to the appropriate join level after SJE removal
|
||||
explain (verbose, costs off)
|
||||
select a2.a
|
||||
from sj b1
|
||||
join sj a1 on b1.b = a1.a
|
||||
join sj a2 on a2.a = a1.a and a2.b = a1.b;
|
||||
|
||||
-- Same, when a semijoin removal happens first
|
||||
explain (verbose, costs off)
|
||||
select a1.a from sj b1 join sj a1 on a1.a = b1.b
|
||||
where exists (select 1 from sj s where s.a = a1.a);
|
||||
|
||||
-- A different case, where modified qual is on a lower join level
|
||||
explain (verbose, costs off)
|
||||
select a2.a
|
||||
from ((sj b1 join sj a1 on true) join sj c1 on c1.b = a1.a)
|
||||
join sj a2 on a2.a = a1.a and a2.b = a1.b;
|
||||
|
||||
--
|
||||
-- SJE corner case: uniqueness of an inner is [partially] derived from
|
||||
-- baserestrictinfo clauses.
|
||||
@@ -3312,8 +3355,7 @@ select 1 from (select y.* from sj x, sj y where x.a = y.a) q,
|
||||
explain (costs off) select * from sj p join sj q on p.a = q.a
|
||||
left join sj r on p.a + q.a = r.a;
|
||||
|
||||
-- FIXME this constant false filter doesn't look good. Should we merge
|
||||
-- equivalence classes?
|
||||
-- Check that we detect constant-false condition after merging ECs.
|
||||
explain (costs off)
|
||||
select * from sj p, sj q where p.a = q.a and p.b = 1 and q.b = 2;
|
||||
|
||||
@@ -3511,6 +3553,18 @@ EXPLAIN (COSTS OFF)
|
||||
SELECT 1 AS c1 FROM sl sl1 LEFT JOIN (sl AS sl2 NATURAL JOIN sl AS sl3)
|
||||
ON sl2.bool_col LEFT JOIN sl AS sl4 ON sl2.bool_col;
|
||||
|
||||
-- Check that quals of a jointree node that becomes empty when the self-join
|
||||
-- is removed are not lost, and that they don't migrate above an outer join
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s, sl t
|
||||
WHERE t.a = s.a AND t.b = s.b;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT t1.a, ss.a FROM sl t1
|
||||
LEFT JOIN (SELECT s.a FROM (SELECT * FROM sl WHERE c IS NOT NULL) s
|
||||
JOIN sl t ON t.a = s.a AND t.b = s.b) ss
|
||||
ON ss.a = t1.a;
|
||||
|
||||
-- Check optimization disabling if it will violate special join conditions.
|
||||
-- Two identical joined relations satisfies self join removal conditions but
|
||||
-- stay in different special join infos.
|
||||
|
||||
@@ -307,6 +307,10 @@ SELECT * FROM document NATURAL JOIN category WHERE f_leak(dtitle) ORDER BY did;
|
||||
SELECT * FROM document TABLESAMPLE BERNOULLI(50) REPEATABLE(0)
|
||||
WHERE f_leak(dtitle) ORDER BY did;
|
||||
|
||||
-- a rel with RLS quals can still be removed by outer-join removal
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT c.cid FROM category c LEFT JOIN document d ON c.cid = d.did;
|
||||
|
||||
-- viewpoint from regress_rls_carol
|
||||
SET SESSION AUTHORIZATION regress_rls_carol;
|
||||
SELECT * FROM document WHERE f_leak(dtitle) ORDER BY did;
|
||||
|
||||
@@ -431,7 +431,6 @@ CatalogId
|
||||
CatalogIdMapEntry
|
||||
CatalogIndexState
|
||||
ChangeContext
|
||||
ChangeVarNodes_callback
|
||||
ChangeVarNodes_context
|
||||
ChannelName
|
||||
CheckPoint
|
||||
|
||||
Reference in New Issue
Block a user