mirror of
https://github.com/postgres/postgres.git
synced 2025-11-28 11:44:57 +03:00
Revisit handling of UNION ALL subqueries with non-Var output columns.
In commit57664ed25eI tried to fix a bug reported by Teodor Sigaev by making non-simple-Var output columns distinct (by wrapping their expressions with dummy PlaceHolderVar nodes). This did not work too well. Commitb28ffd0fccfixed some ensuing problems with matching to child indexes, but per a recent report from Claus Stadler, constraint exclusion of UNION ALL subqueries was still broken, because constant-simplification didn't handle the injected PlaceHolderVars well either. On reflection, the original patch was quite misguided: there is no reason to expect that EquivalenceClass child members will be distinct. So instead of trying to make them so, we should ensure that we can cope with the situation when they're not. Accordingly, this patch reverts the code changes in the above-mentioned commits (though the regression test cases they added stay). Instead, I've added assorted defenses to make sure that duplicate EC child members don't cause any problems. Teodor's original problem ("MergeAppend child's targetlist doesn't match MergeAppend") is addressed more directly by revising prepare_sort_from_pathkeys to let the parent MergeAppend's sort list guide creation of each child's sort list. In passing, get rid of add_sort_column; as far as I can tell, testing for duplicate sort keys at this stage is dead code. Certainly it doesn't trigger often enough to be worth expending cycles on in ordinary queries. And keeping the test would've greatly complicated the new logic in prepare_sort_from_pathkeys, because comparing pathkey list entries against a previous output array requires that we not skip any entries in the list. Back-patch to 9.1, like the previous patches. The only known issue in this area that wasn't caused by the ill-advised previous patches was the MergeAppend planning failure, which of course is not relevant before 9.1. It's possible that we need some of the new defenses against duplicate child EC entries in older branches, but until there's some clear evidence of that I'm going to refrain from back-patching further.
This commit is contained in:
@@ -491,6 +491,15 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
|
||||
* sortref is the SortGroupRef of the originating SortGroupClause, if any,
|
||||
* or zero if not. (It should never be zero if the expression is volatile!)
|
||||
*
|
||||
* If rel is not NULL, it identifies a specific relation we're considering
|
||||
* a path for, and indicates that child EC members for that relation can be
|
||||
* considered. Otherwise child members are ignored. (Note: since child EC
|
||||
* members aren't guaranteed unique, a non-NULL value means that there could
|
||||
* be more than one EC that matches the expression; if so it's order-dependent
|
||||
* which one you get. This is annoying but it only happens in corner cases,
|
||||
* so for now we live with just reporting the first match. See also
|
||||
* generate_implied_equalities_for_indexcol and match_pathkeys_to_index.)
|
||||
*
|
||||
* If create_it is TRUE, we'll build a new EquivalenceClass when there is no
|
||||
* match. If create_it is FALSE, we just return NULL when no match.
|
||||
*
|
||||
@@ -511,6 +520,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
|
||||
Oid opcintype,
|
||||
Oid collation,
|
||||
Index sortref,
|
||||
Relids rel,
|
||||
bool create_it)
|
||||
{
|
||||
EquivalenceClass *newec;
|
||||
@@ -548,6 +558,13 @@ get_eclass_for_sort_expr(PlannerInfo *root,
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
|
||||
/*
|
||||
* Ignore child members unless they match the request.
|
||||
*/
|
||||
if (cur_em->em_is_child &&
|
||||
!bms_equal(cur_em->em_relids, rel))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* If below an outer join, don't match constants: they're not as
|
||||
* constant as they look.
|
||||
@@ -1505,6 +1522,7 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
|
||||
Assert(!cur_em->em_is_child); /* no children yet */
|
||||
if (equal(outervar, cur_em->em_expr))
|
||||
{
|
||||
match = true;
|
||||
@@ -1626,6 +1644,7 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
|
||||
foreach(lc2, cur_ec->ec_members)
|
||||
{
|
||||
coal_em = (EquivalenceMember *) lfirst(lc2);
|
||||
Assert(!coal_em->em_is_child); /* no children yet */
|
||||
if (IsA(coal_em->em_expr, CoalesceExpr))
|
||||
{
|
||||
CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr;
|
||||
@@ -1747,6 +1766,8 @@ exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2)
|
||||
{
|
||||
EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
|
||||
|
||||
if (em->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
if (equal(item1, em->em_expr))
|
||||
item1member = true;
|
||||
else if (equal(item2, em->em_expr))
|
||||
@@ -1800,6 +1821,9 @@ add_child_rel_equivalences(PlannerInfo *root,
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
|
||||
if (cur_em->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
|
||||
/* Does it reference (only) parent_rel? */
|
||||
if (bms_equal(cur_em->em_relids, parent_rel->relids))
|
||||
{
|
||||
@@ -1891,7 +1915,16 @@ find_eclass_clauses_for_index_join(PlannerInfo *root, RelOptInfo *rel,
|
||||
if (!bms_overlap(outer_relids, cur_ec->ec_relids))
|
||||
continue;
|
||||
|
||||
/* Scan members, looking for indexable columns */
|
||||
/*
|
||||
* Scan members, looking for indexable columns. Note
|
||||
* that child EC members are considered, but only when they belong to
|
||||
* the target relation. (Unlike regular members, the same expression
|
||||
* could be a child member of more than one EC. Therefore, it's
|
||||
* potentially order-dependent which EC a child relation's index
|
||||
* column gets matched to. This is annoying but it only happens in
|
||||
* corner cases, so for now we live with just reporting the first
|
||||
* match. See also get_eclass_for_sort_expr.)
|
||||
*/
|
||||
foreach(lc2, cur_ec->ec_members)
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
|
||||
@@ -1914,6 +1947,8 @@ find_eclass_clauses_for_index_join(PlannerInfo *root, RelOptInfo *rel,
|
||||
EquivalenceMember *outer_em = (EquivalenceMember *) lfirst(lc3);
|
||||
Oid eq_op;
|
||||
|
||||
if (outer_em->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
if (!bms_is_subset(outer_em->em_relids, outer_relids))
|
||||
continue;
|
||||
eq_op = select_equality_operator(cur_ec,
|
||||
@@ -2144,8 +2179,10 @@ eclass_useful_for_merging(EquivalenceClass *eclass,
|
||||
{
|
||||
EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
|
||||
|
||||
if (!cur_em->em_is_child &&
|
||||
!bms_overlap(cur_em->em_relids, rel->relids))
|
||||
if (cur_em->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
|
||||
if (!bms_overlap(cur_em->em_relids, rel->relids))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1489,7 +1489,14 @@ match_index_to_pathkeys(IndexOptInfo *index, List *pathkeys)
|
||||
if (pathkey->pk_eclass->ec_has_volatile)
|
||||
return NIL;
|
||||
|
||||
/* Try to match eclass member expression(s) to index */
|
||||
/*
|
||||
* Try to match eclass member expression(s) to index. Note that child
|
||||
* EC members are considered, but only when they belong to the target
|
||||
* relation. (Unlike regular members, the same expression could be a
|
||||
* child member of more than one EC. Therefore, the same index could
|
||||
* be considered to match more than one pathkey list, which is OK
|
||||
* here. See also get_eclass_for_sort_expr.)
|
||||
*/
|
||||
foreach(lc2, pathkey->pk_eclass->ec_members)
|
||||
{
|
||||
EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
|
||||
@@ -2285,15 +2292,6 @@ match_index_to_operand(Node *operand,
|
||||
{
|
||||
int indkey;
|
||||
|
||||
/*
|
||||
* Ignore any PlaceHolderVar nodes above the operand. This is needed so
|
||||
* that we can successfully use expression-index constraints pushed down
|
||||
* through appendrels (UNION ALL). It's safe because a PlaceHolderVar
|
||||
* appearing in a relation-scan-level expression is certainly a no-op.
|
||||
*/
|
||||
while (operand && IsA(operand, PlaceHolderVar))
|
||||
operand = (Node *) ((PlaceHolderVar *) operand)->phexpr;
|
||||
|
||||
/*
|
||||
* Ignore any RelabelType node above the operand. This is needed to be
|
||||
* able to apply indexscanning in binary-compatible-operator cases. Note:
|
||||
|
||||
@@ -224,6 +224,11 @@ canonicalize_pathkeys(PlannerInfo *root, List *pathkeys)
|
||||
* If the PathKey is being generated from a SortGroupClause, sortref should be
|
||||
* the SortGroupClause's SortGroupRef; otherwise zero.
|
||||
*
|
||||
* If rel is not NULL, it identifies a specific relation we're considering
|
||||
* a path for, and indicates that child EC members for that relation can be
|
||||
* considered. Otherwise child members are ignored. (See the comments for
|
||||
* get_eclass_for_sort_expr.)
|
||||
*
|
||||
* create_it is TRUE if we should create any missing EquivalenceClass
|
||||
* needed to represent the sort key. If it's FALSE, we return NULL if the
|
||||
* sort key isn't already present in any EquivalenceClass.
|
||||
@@ -240,6 +245,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root,
|
||||
bool reverse_sort,
|
||||
bool nulls_first,
|
||||
Index sortref,
|
||||
Relids rel,
|
||||
bool create_it,
|
||||
bool canonicalize)
|
||||
{
|
||||
@@ -271,7 +277,7 @@ make_pathkey_from_sortinfo(PlannerInfo *root,
|
||||
/* Now find or (optionally) create a matching EquivalenceClass */
|
||||
eclass = get_eclass_for_sort_expr(root, expr, opfamilies,
|
||||
opcintype, collation,
|
||||
sortref, create_it);
|
||||
sortref, rel, create_it);
|
||||
|
||||
/* Fail if no EC and !create_it */
|
||||
if (!eclass)
|
||||
@@ -323,6 +329,7 @@ make_pathkey_from_sortop(PlannerInfo *root,
|
||||
(strategy == BTGreaterStrategyNumber),
|
||||
nulls_first,
|
||||
sortref,
|
||||
NULL,
|
||||
create_it,
|
||||
canonicalize);
|
||||
}
|
||||
@@ -554,6 +561,7 @@ build_index_pathkeys(PlannerInfo *root,
|
||||
reverse_sort,
|
||||
nulls_first,
|
||||
0,
|
||||
index->rel->relids,
|
||||
false,
|
||||
true);
|
||||
|
||||
@@ -677,6 +685,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
sub_member->em_datatype,
|
||||
sub_eclass->ec_collation,
|
||||
0,
|
||||
rel->relids,
|
||||
false);
|
||||
|
||||
/*
|
||||
@@ -721,6 +730,9 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
Oid sub_expr_coll = sub_eclass->ec_collation;
|
||||
ListCell *k;
|
||||
|
||||
if (sub_member->em_is_child)
|
||||
continue; /* ignore children here */
|
||||
|
||||
foreach(k, sub_tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(k);
|
||||
@@ -760,6 +772,7 @@ convert_subquery_pathkeys(PlannerInfo *root, RelOptInfo *rel,
|
||||
sub_expr_type,
|
||||
sub_expr_coll,
|
||||
0,
|
||||
rel->relids,
|
||||
false);
|
||||
|
||||
/*
|
||||
@@ -951,6 +964,7 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
|
||||
lefttype,
|
||||
((OpExpr *) clause)->inputcollid,
|
||||
0,
|
||||
NULL,
|
||||
true);
|
||||
restrictinfo->right_ec =
|
||||
get_eclass_for_sort_expr(root,
|
||||
@@ -959,6 +973,7 @@ initialize_mergeclause_eclasses(PlannerInfo *root, RestrictInfo *restrictinfo)
|
||||
righttype,
|
||||
((OpExpr *) clause)->inputcollid,
|
||||
0,
|
||||
NULL,
|
||||
true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user