Make Vars be outer-join-aware.

Traditionally we used the same Var struct to represent the value
of a table column everywhere in parse and plan trees.  This choice
predates our support for SQL outer joins, and it's really a pretty
bad idea with outer joins, because the Var's value can depend on
where it is in the tree: it might go to NULL above an outer join.
So expression nodes that are equal() per equalfuncs.c might not
represent the same value, which is a huge correctness hazard for
the planner.

To improve this, decorate Var nodes with a bitmapset showing
which outer joins (identified by RTE indexes) may have nulled
them at the point in the parse tree where the Var appears.
This allows us to trust that equal() Vars represent the same value.
A certain amount of klugery is still needed to cope with cases
where we re-order two outer joins, but it's possible to make it
work without sacrificing that core principle.  PlaceHolderVars
receive similar decoration for the same reason.

In the planner, we include these outer join bitmapsets into the relids
that an expression is considered to depend on, and in consequence also
add outer-join relids to the relids of join RelOptInfos.  This allows
us to correctly perceive whether an expression can be calculated above
or below a particular outer join.

This change affects FDWs that want to plan foreign joins.  They *must*
follow suit when labeling foreign joins in order to match with the
core planner, but for many purposes (if postgres_fdw is any guide)
they'd prefer to consider only base relations within the join.
To support both requirements, redefine ForeignScan.fs_relids as
base+OJ relids, and add a new field fs_base_relids that's set up by
the core planner.

Large though it is, this commit just does the minimum necessary to
install the new mechanisms and get check-world passing again.
Follow-up patches will perform some cleanup.  (The README additions
and comments mention some stuff that will appear in the follow-up.)

Patch by me; thanks to Richard Guo for review.

Discussion: https://postgr.es/m/830269.1656693747@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2023-01-30 13:16:20 -05:00
parent ec7e053a98
commit 2489d76c49
60 changed files with 3896 additions and 984 deletions

View File

@ -4026,7 +4026,17 @@ get_relation_column_alias_ids(Var *node, RelOptInfo *foreignrel,
i = 1;
foreach(lc, foreignrel->reltarget->exprs)
{
if (equal(lfirst(lc), (Node *) node))
Var *tlvar = (Var *) lfirst(lc);
/*
* Match reltarget entries only on varno/varattno. Ideally there
* would be some cross-check on varnullingrels, but it's unclear what
* to do exactly; we don't have enough context to know what that value
* should be.
*/
if (IsA(tlvar, Var) &&
tlvar->varno == node->varno &&
tlvar->varattno == node->varattno)
{
*colno = i;
return;

View File

@ -1517,7 +1517,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
if (fsplan->scan.scanrelid > 0)
rtindex = fsplan->scan.scanrelid;
else
rtindex = bms_next_member(fsplan->fs_relids, -1);
rtindex = bms_next_member(fsplan->fs_base_relids, -1);
rte = exec_rt_fetch(rtindex, estate);
/* Get info about foreign table. */
@ -2414,7 +2414,7 @@ find_modifytable_subplan(PlannerInfo *root,
{
ForeignScan *fscan = (ForeignScan *) subplan;
if (bms_is_member(rtindex, fscan->fs_relids))
if (bms_is_member(rtindex, fscan->fs_base_relids))
return fscan;
}
@ -2838,8 +2838,8 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
* that setrefs.c won't update the string when flattening the
* rangetable. To find out what rtoffset was applied, identify the
* minimum RT index appearing in the string and compare it to the
* minimum member of plan->fs_relids. (We expect all the relids in
* the join will have been offset by the same amount; the Asserts
* minimum member of plan->fs_base_relids. (We expect all the relids
* in the join will have been offset by the same amount; the Asserts
* below should catch it if that ever changes.)
*/
minrti = INT_MAX;
@ -2856,7 +2856,7 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
else
ptr++;
}
rtoffset = bms_next_member(plan->fs_relids, -1) - minrti;
rtoffset = bms_next_member(plan->fs_base_relids, -1) - minrti;
/* Now we can translate the string */
relations = makeStringInfo();
@ -2871,7 +2871,7 @@ postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
char *refname;
rti += rtoffset;
Assert(bms_is_member(rti, plan->fs_relids));
Assert(bms_is_member(rti, plan->fs_base_relids));
rte = rt_fetch(rti, es->rtable);
Assert(rte->rtekind == RTE_RELATION);
/* This logic should agree with explain.c's ExplainTargetRel */

View File

@ -351,6 +351,17 @@ GetForeignJoinPaths(PlannerInfo *root,
it will supply at run time in the tuples it returns.
</para>
<note>
<para>
Beginning with <productname>PostgreSQL</productname> 16,
<structfield>fs_relids</structfield> includes the rangetable indexes
of outer joins, if any were involved in this join. The new field
<structfield>fs_base_relids</structfield> includes only base
relation indexes, and thus
mimics <structfield>fs_relids</structfield>'s old semantics.
</para>
</note>
<para>
See <xref linkend="fdw-planning"/> for additional information.
</para>

View File

@ -1114,7 +1114,7 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
break;
case T_ForeignScan:
*rels_used = bms_add_members(*rels_used,
((ForeignScan *) plan)->fs_relids);
((ForeignScan *) plan)->fs_base_relids);
break;
case T_CustomScan:
*rels_used = bms_add_members(*rels_used,

View File

@ -325,7 +325,7 @@ ExecScanReScan(ScanState *node)
* all of them.
*/
if (IsA(node->ps.plan, ForeignScan))
relids = ((ForeignScan *) node->ps.plan)->fs_relids;
relids = ((ForeignScan *) node->ps.plan)->fs_base_relids;
else if (IsA(node->ps.plan, CustomScan))
relids = ((CustomScan *) node->ps.plan)->custom_relids;
else

View File

@ -80,11 +80,13 @@ makeVar(int varno,
var->varlevelsup = varlevelsup;
/*
* Only a few callers need to make Var nodes with varnosyn/varattnosyn
* different from varno/varattno. We don't provide separate arguments for
* them, but just initialize them to the given varno/varattno. This
* reduces code clutter and chance of error for most callers.
* Only a few callers need to make Var nodes with non-null varnullingrels,
* or with varnosyn/varattnosyn different from varno/varattno. We don't
* provide separate arguments for them, but just initialize them to NULL
* and the given varno/varattno. This reduces code clutter and chance of
* error for most callers.
*/
var->varnullingrels = NULL;
var->varnosyn = (Index) varno;
var->varattnosyn = varattno;

View File

@ -2641,6 +2641,7 @@ expression_tree_mutator_impl(Node *node,
Var *newnode;
FLATCOPY(newnode, var, Var);
/* Assume we need not copy the varnullingrels bitmapset */
return (Node *) newnode;
}
break;
@ -3234,7 +3235,7 @@ expression_tree_mutator_impl(Node *node,
FLATCOPY(newnode, phv, PlaceHolderVar);
MUTATE(newnode->phexpr, phv->phexpr, Expr *);
/* Assume we need not copy the relids bitmapset */
/* Assume we need not copy the relids bitmapsets */
return (Node *) newnode;
}
break;

View File

@ -383,6 +383,11 @@ JumbleExpr(JumbleState *jstate, Node *node)
APP_JUMB(var->varno);
APP_JUMB(var->varattno);
APP_JUMB(var->varlevelsup);
/*
* We can omit varnullingrels, because it's fully determined
* by varno/varlevelsup plus the Var's query location.
*/
}
break;
case T_Const:

View File

@ -295,6 +295,239 @@ Therefore, we don't merge FROM-lists if the result would have too many
FROM-items in one list.
Vars and PlaceHolderVars
------------------------
A Var node is simply the parse-tree representation of a table column
reference. However, in the presence of outer joins, that concept is
more subtle than it might seem. We need to distinguish the values of
a Var "above" and "below" any outer join that could force the Var to
null. As an example, consider
SELECT * FROM t1 LEFT JOIN t2 ON (t1.x = t2.y) WHERE foo(t2.z)
(Assume foo() is not strict, so that we can't reduce the left join to
a plain join.) A naive implementation might try to push the foo(t2.z)
call down to the scan of t2, but that is not correct because
(a) what foo() should actually see for a null-extended join row is NULL,
and (b) if foo() returns false, we should suppress the t1 row from the
join altogether, not emit it with a null-extended t2 row. On the other
hand, it *would* be correct (and desirable) to push that call down to
the scan level if the query were
SELECT * FROM t1 LEFT JOIN t2 ON (t1.x = t2.y AND foo(t2.z))
This motivates considering "t2.z" within the left join's ON clause
to be a different value from "t2.z" outside the JOIN clause. The
former can be identified with t2.z as seen at the relation scan level,
but the latter can't.
Another example occurs in connection with EquivalenceClasses (discussed
below). Given
SELECT * FROM t1 LEFT JOIN t2 ON (t1.x = t2.y) WHERE t1.x = 42
we would like to use the EquivalenceClass mechanisms to derive "t2.y = 42"
to use as a restriction clause for the scan of t2. (That works, because t2
rows having y different from 42 cannot affect the query result.) However,
it'd be wrong to conclude that t2.y will be equal to t1.x in every joined
row. Part of the solution to this problem is to deem that "t2.y" in the
ON clause refers to the relation-scan-level value of t2.y, but not to the
value that y will have in joined rows, where it might be NULL rather than
equal to t1.x.
Therefore, Var nodes are decorated with "varnullingrels", which are sets
of the rangetable indexes of outer joins that potentially null the Var
at the point where it appears in the query. (Using a set, not an ordered
list, is fine since it doesn't matter which join forced the value to null;
and that avoids having to change the representation when we consider
different outer-join orders.) In the examples above, all occurrences of
t1.x would have empty varnullingrels, since the left join doesn't null t1.
The t2 references within the JOIN ON clauses would also have empty
varnullingrels. But outside the JOIN clauses, any Vars referencing t2
would have varnullingrels containing the index of the JOIN's rangetable
entry (RTE), so that they'd be understood as potentially different from
the t2 values seen at scan level. Labeling t2.z in the WHERE clause with
the JOIN's RT index lets us recognize that that occurrence of foo(t2.z)
cannot be pushed down to the t2 scan level: we cannot evaluate that value
at the scan level, but only after the join has been done.
For LEFT and RIGHT outer joins, only Vars coming from the nullable side
of the join are marked with that join's RT index. For FULL joins, Vars
from both inputs are marked. (Such marking doesn't let us tell which
side of the full join a Var came from; but that information can be found
elsewhere at need.)
Notionally, a Var having nonempty varnullingrels can be thought of as
CASE WHEN any-of-these-outer-joins-produced-a-null-extended-row
THEN NULL
ELSE the-scan-level-value-of-the-column
END
It's only notional, because no such calculation is ever done explicitly.
In a finished plan, Vars occurring in scan-level plan nodes represent
the actual table column values, but upper-level Vars are always
references to outputs of lower-level plan nodes. When a join node emits
a null-extended row, it just returns nulls for the relevant output
columns rather than copying up values from its input. Because we don't
ever have to do this calculation explicitly, it's not necessary to
distinguish which side of an outer join got null-extended, which'd
otherwise be essential information for FULL JOIN cases.
Outer join identity 3 (discussed above) complicates this picture
a bit. In the form
A leftjoin (B leftjoin C on (Pbc)) on (Pab)
all of the Vars in clauses Pbc and Pab will have empty varnullingrels,
but if we start with
(A leftjoin B on (Pab)) leftjoin C on (Pbc)
then the parser will have marked Pbc's B Vars with the A/B join's
RT index, making this form artificially different from the first.
For discussion's sake, let's denote this marking with a star:
(A leftjoin B on (Pab)) leftjoin C on (Pb*c)
To cope with this, once we have detected that commuting these joins
is legal, we generate both the Pbc and Pb*c forms of that ON clause,
by either removing or adding the first join's RT index in the B Vars
that the parser created. While generating paths for a plan step that
joins B and C, we include as a relevant join qual only the form that
is appropriate depending on whether A has already been joined to B.
It's also worth noting that identity 3 makes "the left join's RT index"
itself a bit of a fuzzy concept, since the syntactic scope of each join
RTE will depend on which form was produced by the parser. We resolve
this by considering that a left join's identity is determined by its
minimum set of right-hand-side input relations. In both forms allowed
by identity 3, we can identify the first join as having minimum RHS B
and the second join as having minimum RHS C.
Another thing to notice is that C Vars appearing outside the nested
JOIN clauses will be marked as nulled by both left joins if the
original parser input was in the first form of identity 3, but if the
parser input was in the second form, such Vars will only be marked as
nulled by the second join. This is not really a semantic problem:
such Vars will be marked the same way throughout the upper part of the
query, so they will all look equal() which is correct; and they will not
look equal() to any C Var appearing in the JOIN ON clause or below these
joins. However, when building Vars representing the outputs of join
relations, we need to ensure that their varnullingrels are set to
values consistent with the syntactic join order, so that they will
appear equal() to pre-existing Vars in the upper part of the query.
Outer joins also complicate handling of subquery pull-up. Consider
SELECT ..., ss.x FROM tab1
LEFT JOIN (SELECT *, 42 AS x FROM tab2) ss ON ...
We want to be able to pull up the subquery as discussed previously,
but we can't just replace the "ss.x" Var in the top-level SELECT list
with the constant 42. That'd result in always emitting 42, rather
than emitting NULL in null-extended join rows.
To solve this, we introduce the concept of PlaceHolderVars.
A PlaceHolderVar is somewhat like a Var, in that its value originates
at a relation scan level and can then be forced to null by higher-level
outer joins; hence PlaceHolderVars carry a set of nulling rel IDs just
like Vars. Unlike a Var, whose original value comes from a table,
a PlaceHolderVar's original value is defined by a query-determined
expression ("42" in this example); so we represent the PlaceHolderVar
as a node with that expression as child. We insert a PlaceHolderVar
whenever subquery pullup needs to replace a subquery-referencing Var
that has nonempty varnullingrels with an expression that is not simply a
Var. (When the replacement expression is a pulled-up Var, we can just
add the replaced Var's varnullingrels to its set. Also, if the replaced
Var has empty varnullingrels, we don't need a PlaceHolderVar: there is
nothing that'd force the value to null, so the pulled-up expression is
fine to use as-is.) In a finished plan, a PlaceHolderVar becomes just
the contained expression at whatever plan level it's supposed to be
evaluated at, and then upper-level occurrences are replaced by Var
references to that output column of the lower plan level. That causes
the value to go to null when appropriate at an outer join, in the same
way as for normal Vars. Thus, PlaceHolderVars are never seen outside
the planner.
PlaceHolderVars (PHVs) are more complicated than Vars in another way:
their original value might need to be calculated at a join, not a
base-level relation scan. This can happen when a pulled-up subquery
contains a join. Because of this, a PHV can create a join order
constraint that wouldn't otherwise exist, to ensure that it can
be calculated before it is used. A PHV's expression can also contain
LATERAL references, adding complications that are discussed below.
Relation Identification and Qual Clause Placement
-------------------------------------------------
A qual clause obtained from WHERE or JOIN/ON can be enforced at the lowest
scan or join level that includes all relations used in the clause. For
this purpose we consider that outer joins listed in varnullingrels or
phnullingrels are used in the clause, since we can't compute the qual's
result correctly until we know whether such Vars have gone to null.
The one exception to this general rule is that a non-degenerate outer
JOIN/ON qual (one that references the non-nullable side of the join)
cannot be enforced below that join, even if it doesn't reference the
nullable side. Pushing it down into the non-nullable side would result
in rows disappearing from the join's result, rather than appearing as
null-extended rows. To handle that, when we identify such a qual we
artificially add the join's minimum input relid set to the set of
relations it is considered to use, forcing it to be evaluated exactly at
that join level. The same happens for outer-join quals that mention no
relations at all.
When attaching a qual clause to a join plan node that is performing an
outer join, the qual clause is considered a "join clause" (that is, it is
applied before the join performs null-extension) if it does not reference
that outer join in any varnullingrels or phnullingrels set, or a "filter
clause" (applied after null-extension) if it does reference that outer
join. A qual clause that originally appeared in that outer join's JOIN/ON
will fall into the first category, since the parser would not have marked
any of its Vars as referencing the outer join. A qual clause that
originally came from some upper ON clause or WHERE clause will be seen as
referencing the outer join if it references any of the nullable side's
Vars, since those Vars will be so marked by the parser. But, if such a
qual does not reference any nullable-side Vars, it's okay to push it down
into the non-nullable side, so it won't get attached to the join node in
the first place.
These things lead us to identify join relations within the planner
by the sets of base relation RT indexes plus outer join RT indexes
that they include. In that way, the sets of relations used by qual
clauses can be directly compared to join relations' relid sets to
see where to place the clauses. These identifying sets are unique
because, for any given collection of base relations, there is only
one valid set of outer joins to have performed along the way to
joining that set of base relations (although the order of applying
them could vary, as discussed above).
SEMI joins do not have RT indexes, because they are artifacts made by
the planner rather than the parser. (We could create rangetable
entries for them, but there seems no need at present.) This does not
cause a problem for qual placement, because the nullable side of a
semijoin is not referenceable from above the join, so there is never a
need to cite it in varnullingrels or phnullingrels. It does not cause a
problem for join relation identification either, since whether a semijoin
has been completed is again implicit in the set of base relations
included in the join.
There is one additional complication for qual clause placement, which
occurs when we have made multiple versions of an outer-join clause as
described previously (that is, we have both "Pbc" and "Pb*c" forms of
the same clause seen in outer join identity 3). When forming an outer
join we only want to apply one of the redundant versions of the clause.
If we are forming the B/C join without having yet computed the A/B
join, it's easy to reject the "Pb*c" form since its required relid
set includes the A/B join relid which is not in the input. However,
if we form B/C after A/B, then both forms of the clause are applicable
so far as that test can tell. We have to look more closely to notice
that the "Pbc" clause form refers to relation B which is no longer
directly accessible. While this check is straightforward, it's not
especially cheap (see clause_is_computable_at()). To avoid doing it
unnecessarily, we mark the variant versions of a redundant clause as
either "has_clone" or "is_clone". When considering a clone clause,
we must check clause_is_computable_at() to disentangle which version
to apply at the current join level. (In debug builds, we also Assert
that non-clone clauses are validly computable at the current level;
but that seems too expensive for production usage.)
Optimizer Functions
-------------------
@ -437,11 +670,10 @@ inputs.
EquivalenceClasses
------------------
During the deconstruct_jointree() scan of the query's qual clauses, we look
for mergejoinable equality clauses A = B whose applicability is not delayed
by an outer join; these are called "equivalence clauses". When we find
one, we create an EquivalenceClass containing the expressions A and B to
record this knowledge. If we later find another equivalence clause B = C,
During the deconstruct_jointree() scan of the query's qual clauses, we
look for mergejoinable equality clauses A = B. When we find one, we
create an EquivalenceClass containing the expressions A and B to record
that they are equal. If we later find another equivalence clause B = C,
we add C to the existing EquivalenceClass for {A B}; this may require
merging two existing EquivalenceClasses. At the end of the scan, we have
sets of values that are known all transitively equal to each other. We can
@ -473,15 +705,89 @@ asserts that at any plan node where more than one of its member values
can be computed, output rows in which the values are not all equal may
be discarded without affecting the query result. (We require all levels
of the plan to enforce EquivalenceClasses, hence a join need not recheck
equality of values that were computable by one of its children.) For an
ordinary EquivalenceClass that is "valid everywhere", we can further infer
that the values are all non-null, because all mergejoinable operators are
strict. However, we also allow equivalence clauses that appear below the
nullable side of an outer join to form EquivalenceClasses; for these
classes, the interpretation is that either all the values are equal, or
all (except pseudo-constants) have gone to null. (This requires a
limitation that non-constant members be strict, else they might not go
to null when the other members do.) Consider for example
equality of values that were computable by one of its children.)
Outer joins complicate this picture quite a bit, however. While we could
theoretically use mergejoinable equality clauses that appear in outer-join
conditions as sources of EquivalenceClasses, there's a serious difficulty:
the resulting deductions are not valid everywhere. For example, given
SELECT * FROM a LEFT JOIN b ON (a.x = b.y AND a.x = 42);
we can safely derive b.y = 42 and use that in the scan of B, because B
rows not having b.y = 42 will not contribute to the join result. However,
we cannot apply a.x = 42 at the scan of A, or we will remove rows that
should appear in the join result. We could apply a.x = 42 as an outer join
condition (and then it would be unnecessary to also check a.x = b.y).
This is not yet implemented, however.
A related issue is that constants appearing below an outer join are
less constant than they appear. Ordinarily, if we find "A = 1" and
"B = 1", it's okay to put A and B into the same EquivalenceClass.
But consider
SELECT * FROM a
LEFT JOIN (SELECT * FROM b WHERE b.z = 1) b ON (a.x = b.y)
WHERE a.x = 1;
It would be a serious error to conclude that a.x = b.z, so we cannot
form a single EquivalenceClass {a.x b.z 1}.
This leads to considering EquivalenceClasses as applying within "join
domains", which are sets of relations that are inner-joined to each other.
(We can treat semijoins as if they were inner joins for this purpose.)
There is a top-level join domain, and then each outer join in the query
creates a new join domain comprising its nullable side. Full joins create
two join domains, one for each side. EquivalenceClasses generated from
WHERE are associated with the top-level join domain. EquivalenceClasses
generated from the ON clause of an outer join are associated with the
domain created by that outer join. EquivalenceClasses generated from the
ON clause of an inner or semi join are associated with the syntactically
most closely nested join domain.
Having defined these domains, we can fix the not-so-constant-constants
problem by considering that constants only match EquivalenceClass members
when they come from clauses within the same join domain. In the above
example, this means we keep {a.x 1} and {b.z 1} as separate
EquivalenceClasses and don't erroneously merge them. We don't have to
worry about this for Vars (or expressions containing Vars), because
references to the "same" column from different join domains will have
different varnullingrels and thus won't be equal() anyway.
In the future, the join-domain concept may allow us to treat mergejoinable
outer-join conditions as sources of EquivalenceClasses. The idea would be
that conditions derived from such classes could only be enforced at scans
or joins that are within the appropriate join domain. This is not
implemented yet, however, as the details are trickier than they appear.
Another instructive example is:
SELECT *
FROM a LEFT JOIN
(SELECT * FROM b JOIN c ON b.y = c.z WHERE b.y = 10) ss
ON a.x = ss.y
ORDER BY ss.y;
We can form the EquivalenceClass {b.y c.z 10} and thereby apply c.z = 10
while scanning C, as well as b.y = 10 while scanning B, so that no clause
needs to be checked at the inner join. The left-join clause "a.x = ss.y"
(really "a.x = b.y") is not considered an equivalence clause, so we do
not insert a.x into that same EquivalenceClass; if we did, we'd falsely
conclude a.x = 10. In the future though we might be able to do that,
if we can keep from applying a.x = 10 at the scan of A, which in principle
we could do by noting that the EquivalenceClass only applies within the
{B,C} join domain.
Also notice that ss.y in the ORDER BY is really b.y* (that is, the
possibly-nulled form of b.y), so we will not confuse it with the b.y member
of the lower EquivalenceClass. Thus, we won't mistakenly conclude that
that ss.y is equal to a constant, which if true would lead us to think that
sorting for the ORDER BY is unnecessary (see discussion of PathKeys below).
Instead, there will be a separate EquivalenceClass containing only b.y*,
which will form the basis for the PathKey describing the required sort
order.
Also consider this variant:
SELECT *
FROM a LEFT JOIN
@ -489,27 +795,42 @@ to null when the other members do.) Consider for example
ON a.x = ss.y
WHERE a.x = 42;
We can form the below-outer-join EquivalenceClass {b.y c.z 10} and thereby
apply c.z = 10 while scanning c. (The reason we disallow outerjoin-delayed
clauses from forming EquivalenceClasses is exactly that we want to be able
to push any derived clauses as far down as possible.) But once above the
outer join it's no longer necessarily the case that b.y = 10, and thus we
cannot use such EquivalenceClasses to conclude that sorting is unnecessary
(see discussion of PathKeys below).
We still form the EquivalenceClass {b.y c.z 10}, and additionally
we have an EquivalenceClass {a.x 42} belonging to a different join domain.
We cannot use "a.x = b.y" to merge these classes. However, we can compare
that outer join clause to the existing EquivalenceClasses and form the
derived clause "b.y = 42", which we can treat as a valid equivalence
within the lower join domain (since no row of that domain not having
b.y = 42 can contribute to the outer-join result). That makes the lower
EquivalenceClass {42 b.y c.z 10}, resulting in the contradiction 10 = 42,
which lets the planner deduce that the B/C join need not be computed at
all: the result of that whole join domain can be forced to empty.
(This gets implemented as a gating Result filter, since more usually the
potential contradiction involves Param values rather than just Consts, and
thus it has to be checked at runtime. We can use the join domain to
determine the join level at which to place the gating condition.)
In this example, notice also that a.x = ss.y (really a.x = b.y) is not an
equivalence clause because its applicability to b is delayed by the outer
join; thus we do not try to insert b.y into the equivalence class {a.x 42}.
But since we see that a.x has been equated to 42 above the outer join, we
are able to form a below-outer-join class {b.y 42}; this restriction can be
added because no b/c row not having b.y = 42 can contribute to the result
of the outer join, and so we need not compute such rows. Now this class
will get merged with {b.y c.z 10}, leading to the contradiction 10 = 42,
which lets the planner deduce that the b/c join need not be computed at all
because none of its rows can contribute to the outer join. (This gets
implemented as a gating Result filter, since more usually the potential
contradiction involves Param values rather than just Consts, and thus has
to be checked at runtime.)
There is an additional complication when re-ordering outer joins according
to identity 3. Recall that the two choices we consider for such joins are
A leftjoin (B leftjoin C on (Pbc)) on (Pab)
(A leftjoin B on (Pab)) leftjoin C on (Pb*c)
where the star denotes varnullingrels markers on B's Vars. When Pbc
is (or includes) a mergejoinable clause, we have something like
A leftjoin (B leftjoin C on (b.b = c.c)) on (Pab)
(A leftjoin B on (Pab)) leftjoin C on (b.b* = c.c)
We could generate an EquivalenceClause linking b.b and c.c, but if we
then also try to link b.b* and c.c, we end with a nonsensical conclusion
that b.b and b.b* are equal (at least in some parts of the plan tree).
In any case, the conclusions we could derive from such a thing would be
largely duplicative. Conditions involving b.b* can't be computed below
this join nest, while any conditions that can be computed would be
duplicative of what we'd get from the b.b/c.c combination. Therefore,
we choose to generate an EquivalenceClause linking b.b and c.c, but
"b.b* = c.c" is handled as just an ordinary clause.
To aid in determining the sort ordering(s) that can work with a mergejoin,
we mark each mergejoinable clause with the EquivalenceClasses of its left
@ -522,7 +843,11 @@ if other equivalence clauses are later found to bear on the same
expressions.
Another way that we may form a single-item EquivalenceClass is in creation
of a PathKey to represent a desired sort order (see below). This is a bit
of a PathKey to represent a desired sort order (see below). This happens
if an ORDER BY or GROUP BY key is not mentioned in any equivalence
clause. We need to reason about sort orders in such queries, and our
representation of sort ordering is a PathKey which depends on an
EquivalenceClass, so we have to make an EquivalenceClass. This is a bit
different from the above cases because such an EquivalenceClass might
contain an aggregate function or volatile expression. (A clause containing
a volatile function will never be considered mergejoinable, even if its top
@ -544,6 +869,9 @@ it's possible that it belongs to more than one. We keep track of all the
families to ensure that we can make use of an index belonging to any one of
the families for mergejoin purposes.)
For the same sort of reason, an EquivalenceClass is also associated
with a particular collation, if its datatype(s) care about collation.
An EquivalenceClass can contain "em_is_child" members, which are copies
of members that contain appendrel parent relation Vars, transposed to
contain the equivalent child-relation variables or expressions. These
@ -579,7 +907,7 @@ Index scans have Path.pathkeys that represent the chosen index's ordering,
if any. A single-key index would create a single-PathKey list, while a
multi-column index generates a list with one element per key index column.
Non-key columns specified in the INCLUDE clause of covering indexes don't
have corresponding PathKeys in the list, because the have no influence on
have corresponding PathKeys in the list, because they have no influence on
index ordering. (Actually, since an index can be scanned either forward or
backward, there are two possible sort orders and two possible PathKey lists
it can generate.)
@ -608,9 +936,14 @@ must now be ordered too. This is true even though we used neither an
explicit sort nor a mergejoin on Y. (Note: hash joins cannot be counted
on to preserve the order of their outer relation, because the executor
might decide to "batch" the join, so we always set pathkeys to NIL for
a hashjoin path.) Exception: a RIGHT or FULL join doesn't preserve the
ordering of its outer relation, because it might insert nulls at random
points in the ordering.
a hashjoin path.)
An outer join doesn't preserve the ordering of its nullable input
relation(s), because it might insert nulls at random points in the
ordering. We don't need to think about this explicitly in the PathKey
representation, because a PathKey representing a post-join variable
will contain varnullingrel bits, making it not equal to a PathKey
representing the pre-join value.
In general, we can justify using EquivalenceClasses as the basis for
pathkeys because, whenever we scan a relation containing multiple
@ -655,14 +988,9 @@ redundancy, we save time and improve planning, since the planner will more
easily recognize equivalent orderings as being equivalent.
Another interesting property is that if the underlying EquivalenceClass
contains a constant and is not below an outer join, then the pathkey is
completely redundant and need not be sorted by at all! Every row must
contain the same constant value, so there's no need to sort. (If the EC is
below an outer join, we still have to sort, since some of the rows might
have gone to null and others not. In this case we must be careful to pick
a non-const member to sort by. The assumption that all the non-const
members go to null at the same plan level is critical here, else they might
not produce the same sort order.) This might seem pointless because users
contains a constant, then the pathkey is completely redundant and need not
be sorted by at all! Every interesting row must contain the same value,
so there's no need to sort. This might seem pointless because users
are unlikely to write "... WHERE x = 42 ORDER BY x", but it allows us to
recognize when particular index columns are irrelevant to the sort order:
if we have "... WHERE x = 42 ORDER BY y", scanning an index on (x,y)
@ -670,15 +998,6 @@ produces correctly ordered data without a sort step. We used to have very
ugly ad-hoc code to recognize that in limited contexts, but discarding
constant ECs from pathkeys makes it happen cleanly and automatically.
You might object that a below-outer-join EquivalenceClass doesn't always
represent the same values at every level of the join tree, and so using
it to uniquely identify a sort order is dubious. This is true, but we
can avoid dealing with the fact explicitly because we always consider that
an outer join destroys any ordering of its nullable inputs. Thus, even
if a path was sorted by {a.x} below an outer join, we'll re-sort if that
sort ordering was important; and so using the same PathKey for both sort
orderings doesn't create any real problem.
Order of processing for EquivalenceClasses and PathKeys
-------------------------------------------------------

View File

@ -273,7 +273,7 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, int num_gene,
* rel once we know the final targetlist (see
* grouping_planner).
*/
if (!bms_equal(joinrel->relids, root->all_baserels))
if (!bms_equal(joinrel->relids, root->all_query_rels))
generate_useful_gather_paths(root, joinrel, false);
/* Find and save the cheapest paths for this joinrel */

View File

@ -159,27 +159,6 @@ make_one_rel(PlannerInfo *root, List *joinlist)
Index rti;
double total_pages;
/*
* Construct the all_baserels Relids set.
*/
root->all_baserels = NULL;
for (rti = 1; rti < root->simple_rel_array_size; rti++)
{
RelOptInfo *brel = root->simple_rel_array[rti];
/* there may be empty slots corresponding to non-baserel RTEs */
if (brel == NULL)
continue;
Assert(brel->relid == rti); /* sanity check on array */
/* ignore RTEs that are "other rels" */
if (brel->reloptkind != RELOPT_BASEREL)
continue;
root->all_baserels = bms_add_member(root->all_baserels, brel->relid);
}
/* Mark base rels as to whether we care about fast-start plans */
set_base_rel_consider_startup(root);
@ -207,6 +186,7 @@ make_one_rel(PlannerInfo *root, List *joinlist)
{
RelOptInfo *brel = root->simple_rel_array[rti];
/* there may be empty slots corresponding to non-baserel RTEs */
if (brel == NULL)
continue;
@ -231,9 +211,9 @@ make_one_rel(PlannerInfo *root, List *joinlist)
rel = make_rel_from_joinlist(root, joinlist);
/*
* The result should join all and only the query's base rels.
* The result should join all and only the query's base + outer-join rels.
*/
Assert(bms_equal(rel->relids, root->all_baserels));
Assert(bms_equal(rel->relids, root->all_query_rels));
return rel;
}
@ -558,7 +538,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
* the final scan/join targetlist is available (see grouping_planner).
*/
if (rel->reloptkind == RELOPT_BASEREL &&
!bms_equal(rel->relids, root->all_baserels))
!bms_equal(rel->relids, root->all_query_rels))
generate_useful_gather_paths(root, rel, false);
/* Now find the cheapest of the paths for this rel */
@ -879,7 +859,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
* to support an uncommon usage of second-rate sampling methods. Instead,
* if there is a risk that the query might perform an unsafe join, just
* wrap the SampleScan in a Materialize node. We can check for joins by
* counting the membership of all_baserels (note that this correctly
* counting the membership of all_query_rels (note that this correctly
* counts inheritance trees as single rels). If we're inside a subquery,
* we can't easily check whether a join might occur in the outer query, so
* just assume one is possible.
@ -888,7 +868,7 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
* so check repeatable_across_scans last, even though that's a bit odd.
*/
if ((root->query_level > 1 ||
bms_membership(root->all_baserels) != BMS_SINGLETON) &&
bms_membership(root->all_query_rels) != BMS_SINGLETON) &&
!(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans))
{
path = (Path *) create_material_path(rel, path);
@ -970,7 +950,7 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
if (enable_partitionwise_join &&
rel->reloptkind == RELOPT_BASEREL &&
rte->relkind == RELKIND_PARTITIONED_TABLE &&
rel->attr_needed[InvalidAttrNumber - rel->min_attr] == NULL)
bms_is_empty(rel->attr_needed[InvalidAttrNumber - rel->min_attr]))
rel->consider_partitionwise_join = true;
/*
@ -3429,7 +3409,7 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
* partial paths. We'll do the same for the topmost scan/join rel
* once we know the final targetlist (see grouping_planner).
*/
if (!bms_equal(rel->relids, root->all_baserels))
if (!bms_equal(rel->relids, root->all_query_rels))
generate_useful_gather_paths(root, rel, false);
/* Find and save the cheapest paths for this rel */

View File

@ -218,7 +218,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
if (rinfo)
{
ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
ok = (rinfo->num_base_rels == 1) &&
(is_pseudo_constant_clause_relids(lsecond(expr->args),
rinfo->right_relids) ||
(varonleft = false,
@ -579,30 +579,6 @@ find_single_rel_for_clauses(PlannerInfo *root, List *clauses)
return NULL; /* no clauses */
}
/*
* bms_is_subset_singleton
*
* Same result as bms_is_subset(s, bms_make_singleton(x)),
* but a little faster and doesn't leak memory.
*
* Is this of use anywhere else? If so move to bitmapset.c ...
*/
static bool
bms_is_subset_singleton(const Bitmapset *s, int x)
{
switch (bms_membership(s))
{
case BMS_EMPTY_SET:
return true;
case BMS_SINGLETON:
return bms_is_member(x, s);
case BMS_MULTIPLE:
return false;
}
/* can't get here... */
return false;
}
/*
* treat_as_join_clause -
* Decide whether an operator clause is to be handled by the
@ -631,17 +607,20 @@ treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo,
else
{
/*
* Otherwise, it's a join if there's more than one relation used. We
* can optimize this calculation if an rinfo was passed.
* Otherwise, it's a join if there's more than one base relation used.
* We can optimize this calculation if an rinfo was passed.
*
* XXX Since we know the clause is being evaluated at a join, the
* only way it could be single-relation is if it was delayed by outer
* joins. Although we can make use of the restriction qual estimators
* anyway, it seems likely that we ought to account for the
* probability of injected nulls somehow.
* joins. We intentionally count only baserels here, not OJs that
* might be present in rinfo->clause_relids, so that we direct such
* cases to the restriction qual estimators not join estimators.
* Eventually some notice should be taken of the possibility of
* injected nulls, but we'll likely want to do that in the restriction
* estimators rather than starting to treat such cases as join quals.
*/
if (rinfo)
return (bms_membership(rinfo->clause_relids) == BMS_MULTIPLE);
return (rinfo->num_base_rels > 1);
else
return (NumRelids(root, clause) > 1);
}
@ -754,7 +733,9 @@ clause_selectivity_ext(PlannerInfo *root,
* for all non-JOIN_INNER cases.
*/
if (varRelid == 0 ||
bms_is_subset_singleton(rinfo->clause_relids, varRelid))
rinfo->num_base_rels == 0 ||
(rinfo->num_base_rels == 1 &&
bms_is_member(varRelid, rinfo->clause_relids)))
{
/* Cacheable --- do we already have the result? */
if (jointype == JOIN_INNER)

View File

@ -4781,6 +4781,10 @@ compute_semi_anti_join_factors(PlannerInfo *root,
norm_sjinfo.syn_lefthand = outerrel->relids;
norm_sjinfo.syn_righthand = innerrel->relids;
norm_sjinfo.jointype = JOIN_INNER;
norm_sjinfo.ojrelid = 0;
norm_sjinfo.commute_above_l = NULL;
norm_sjinfo.commute_above_r = NULL;
norm_sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
norm_sjinfo.lhs_strict = false;
norm_sjinfo.delay_upper_joins = false;
@ -4946,6 +4950,10 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals)
sjinfo.syn_lefthand = path->outerjoinpath->parent->relids;
sjinfo.syn_righthand = path->innerjoinpath->parent->relids;
sjinfo.jointype = JOIN_INNER;
sjinfo.ojrelid = 0;
sjinfo.commute_above_l = NULL;
sjinfo.commute_above_r = NULL;
sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo.lhs_strict = false;
sjinfo.delay_upper_joins = false;

View File

@ -29,12 +29,14 @@
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
#include "optimizer/restrictinfo.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
static EquivalenceMember *add_eq_member(EquivalenceClass *ec,
Expr *expr, Relids relids, Relids nullable_relids,
bool is_child, Oid datatype);
EquivalenceMember *parent,
Oid datatype);
static bool is_exprlist_member(Expr *node, List *exprs);
static void generate_base_implied_equalities_const(PlannerInfo *root,
EquivalenceClass *ec);
@ -61,10 +63,10 @@ static RestrictInfo *create_join_clause(PlannerInfo *root,
EquivalenceMember *rightem,
EquivalenceClass *parent_ec);
static bool reconsider_outer_join_clause(PlannerInfo *root,
RestrictInfo *rinfo,
OuterJoinClauseInfo *ojcinfo,
bool outer_on_left);
static bool reconsider_full_join_clause(PlannerInfo *root,
RestrictInfo *rinfo);
OuterJoinClauseInfo *ojcinfo);
static Bitmapset *get_eclass_indexes_for_relids(PlannerInfo *root,
Relids relids);
static Bitmapset *get_common_eclass_indexes(PlannerInfo *root, Relids relids1,
@ -399,7 +401,7 @@ process_equivalence(PlannerInfo *root,
{
/* Case 3: add item2 to ec1 */
em2 = add_eq_member(ec1, item2, item2_relids, item2_nullable_relids,
false, item2_type);
NULL, item2_type);
ec1->ec_sources = lappend(ec1->ec_sources, restrictinfo);
ec1->ec_below_outer_join |= below_outer_join;
ec1->ec_min_security = Min(ec1->ec_min_security,
@ -417,7 +419,7 @@ process_equivalence(PlannerInfo *root,
{
/* Case 3: add item1 to ec2 */
em1 = add_eq_member(ec2, item1, item1_relids, item1_nullable_relids,
false, item1_type);
NULL, item1_type);
ec2->ec_sources = lappend(ec2->ec_sources, restrictinfo);
ec2->ec_below_outer_join |= below_outer_join;
ec2->ec_min_security = Min(ec2->ec_min_security,
@ -451,9 +453,9 @@ process_equivalence(PlannerInfo *root,
ec->ec_max_security = restrictinfo->security_level;
ec->ec_merged = NULL;
em1 = add_eq_member(ec, item1, item1_relids, item1_nullable_relids,
false, item1_type);
NULL, item1_type);
em2 = add_eq_member(ec, item2, item2_relids, item2_nullable_relids,
false, item2_type);
NULL, item2_type);
root->eq_classes = lappend(root->eq_classes, ec);
@ -543,7 +545,7 @@ canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation)
*/
static EquivalenceMember *
add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
Relids nullable_relids, bool is_child, Oid datatype)
Relids nullable_relids, EquivalenceMember *parent, Oid datatype)
{
EquivalenceMember *em = makeNode(EquivalenceMember);
@ -551,8 +553,9 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
em->em_relids = relids;
em->em_nullable_relids = nullable_relids;
em->em_is_const = false;
em->em_is_child = is_child;
em->em_is_child = (parent != NULL);
em->em_datatype = datatype;
em->em_parent = parent;
if (bms_is_empty(relids))
{
@ -564,12 +567,12 @@ add_eq_member(EquivalenceClass *ec, Expr *expr, Relids relids,
* get_eclass_for_sort_expr() has to work harder. We put the tests
* there not here to save cycles in the equivalence case.
*/
Assert(!is_child);
Assert(!parent);
em->em_is_const = true;
ec->ec_has_const = true;
/* it can't affect ec_relids */
}
else if (!is_child) /* child members don't add to ec_relids */
else if (!parent) /* child members don't add to ec_relids */
{
ec->ec_relids = bms_add_members(ec->ec_relids, relids);
}
@ -722,7 +725,7 @@ get_eclass_for_sort_expr(PlannerInfo *root,
nullable_relids = bms_intersect(nullable_relids, expr_relids);
newem = add_eq_member(newec, copyObject(expr), expr_relids,
nullable_relids, false, opcintype);
nullable_relids, NULL, opcintype);
/*
* add_eq_member doesn't check for volatile functions, set-returning
@ -757,6 +760,12 @@ get_eclass_for_sort_expr(PlannerInfo *root,
{
RelOptInfo *rel = root->simple_rel_array[i];
if (rel == NULL) /* must be an outer join */
{
Assert(bms_is_member(i, root->outer_join_rels));
continue;
}
Assert(rel->reloptkind == RELOPT_BASEREL ||
rel->reloptkind == RELOPT_DEADREL);
@ -1113,6 +1122,12 @@ generate_base_implied_equalities(PlannerInfo *root)
{
RelOptInfo *rel = root->simple_rel_array[i];
if (rel == NULL) /* must be an outer join */
{
Assert(bms_is_member(i, root->outer_join_rels));
continue;
}
Assert(rel->reloptkind == RELOPT_BASEREL);
rel->eclass_indexes = bms_add_member(rel->eclass_indexes,
@ -1808,6 +1823,7 @@ create_join_clause(PlannerInfo *root,
EquivalenceClass *parent_ec)
{
RestrictInfo *rinfo;
RestrictInfo *parent_rinfo = NULL;
ListCell *lc;
MemoryContext oldcontext;
@ -1852,6 +1868,20 @@ create_join_clause(PlannerInfo *root,
*/
oldcontext = MemoryContextSwitchTo(root->planner_cxt);
/*
* If either EM is a child, recursively create the corresponding
* parent-to-parent clause, so that we can duplicate its rinfo_serial.
*/
if (leftem->em_is_child || rightem->em_is_child)
{
EquivalenceMember *leftp = leftem->em_parent ? leftem->em_parent : leftem;
EquivalenceMember *rightp = rightem->em_parent ? rightem->em_parent : rightem;
parent_rinfo = create_join_clause(root, ec, opno,
leftp, rightp,
parent_ec);
}
rinfo = build_implied_join_equality(root,
opno,
ec->ec_collation,
@ -1863,6 +1893,10 @@ create_join_clause(PlannerInfo *root,
rightem->em_nullable_relids),
ec->ec_min_security);
/* If it's a child clause, copy the parent's rinfo_serial */
if (parent_rinfo)
rinfo->rinfo_serial = parent_rinfo->rinfo_serial;
/* Mark the clause as redundant, or not */
rinfo->parent_ec = parent_ec;
@ -1977,10 +2011,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* Process the LEFT JOIN clauses */
foreach(cell, root->left_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
if (reconsider_outer_join_clause(root, rinfo, true))
if (reconsider_outer_join_clause(root, ojcinfo, true))
{
RestrictInfo *rinfo = ojcinfo->rinfo;
found = true;
/* remove it from the list */
root->left_join_clauses =
@ -1996,10 +2032,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* Process the RIGHT JOIN clauses */
foreach(cell, root->right_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
if (reconsider_outer_join_clause(root, rinfo, false))
if (reconsider_outer_join_clause(root, ojcinfo, false))
{
RestrictInfo *rinfo = ojcinfo->rinfo;
found = true;
/* remove it from the list */
root->right_join_clauses =
@ -2015,10 +2053,12 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* Process the FULL JOIN clauses */
foreach(cell, root->full_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
if (reconsider_full_join_clause(root, rinfo))
if (reconsider_full_join_clause(root, ojcinfo))
{
RestrictInfo *rinfo = ojcinfo->rinfo;
found = true;
/* remove it from the list */
root->full_join_clauses =
@ -2035,21 +2075,21 @@ reconsider_outer_join_clauses(PlannerInfo *root)
/* Now, any remaining clauses have to be thrown back */
foreach(cell, root->left_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
distribute_restrictinfo_to_rels(root, rinfo);
distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
}
foreach(cell, root->right_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
distribute_restrictinfo_to_rels(root, rinfo);
distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
}
foreach(cell, root->full_join_clauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(cell);
OuterJoinClauseInfo *ojcinfo = (OuterJoinClauseInfo *) lfirst(cell);
distribute_restrictinfo_to_rels(root, rinfo);
distribute_restrictinfo_to_rels(root, ojcinfo->rinfo);
}
}
@ -2059,9 +2099,10 @@ reconsider_outer_join_clauses(PlannerInfo *root)
* Returns true if we were able to propagate a constant through the clause.
*/
static bool
reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
reconsider_outer_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo,
bool outer_on_left)
{
RestrictInfo *rinfo = ojcinfo->rinfo;
Expr *outervar,
*innervar;
Oid opno,
@ -2185,8 +2226,11 @@ reconsider_outer_join_clause(PlannerInfo *root, RestrictInfo *rinfo,
* Returns true if we were able to propagate a constant through the clause.
*/
static bool
reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
reconsider_full_join_clause(PlannerInfo *root, OuterJoinClauseInfo *ojcinfo)
{
RestrictInfo *rinfo = ojcinfo->rinfo;
SpecialJoinInfo *sjinfo = ojcinfo->sjinfo;
Relids fjrelids = bms_make_singleton(sjinfo->ojrelid);
Expr *leftvar;
Expr *rightvar;
Oid opno,
@ -2268,6 +2312,18 @@ reconsider_full_join_clause(PlannerInfo *root, RestrictInfo *rinfo)
cfirst = (Node *) linitial(cexpr->args);
csecond = (Node *) lsecond(cexpr->args);
/*
* The COALESCE arguments will be marked as possibly nulled by
* the full join, while we wish to generate clauses that apply
* to the join's inputs. So we must strip the join from the
* nullingrels fields of cfirst/csecond before comparing them
* to leftvar/rightvar. (Perhaps with a less hokey
* representation for FULL JOIN USING output columns, this
* wouldn't be needed?)
*/
cfirst = remove_nulling_relids(cfirst, fjrelids, NULL);
csecond = remove_nulling_relids(csecond, fjrelids, NULL);
if (equal(leftvar, cfirst) && equal(rightvar, csecond))
{
coal_idx = foreach_current_index(lc2);
@ -2605,10 +2661,18 @@ add_child_rel_equivalences(PlannerInfo *root,
if (cur_em->em_is_child)
continue; /* ignore children here */
/* Does this member reference child's topmost parent rel? */
if (bms_overlap(cur_em->em_relids, top_parent_relids))
/*
* Consider only members that reference and can be computed at
* child's topmost parent rel. In particular we want to exclude
* parent-rel Vars that have nonempty varnullingrels. Translating
* those might fail, if the transformed expression wouldn't be a
* simple Var; and in any case it wouldn't produce a member that
* has any use in creating plans for the child rel.
*/
if (bms_is_subset(cur_em->em_relids, top_parent_relids) &&
!bms_is_empty(cur_em->em_relids))
{
/* Yes, generate transformed child version */
/* OK, generate transformed child version */
Expr *child_expr;
Relids new_relids;
Relids new_nullable_relids;
@ -2656,7 +2720,7 @@ add_child_rel_equivalences(PlannerInfo *root,
(void) add_eq_member(cur_ec, child_expr,
new_relids, new_nullable_relids,
true, cur_em->em_datatype);
cur_em, cur_em->em_datatype);
/* Record this EC index for the child rel */
child_rel->eclass_indexes = bms_add_member(child_rel->eclass_indexes, i);
@ -2797,7 +2861,7 @@ add_child_join_rel_equivalences(PlannerInfo *root,
(void) add_eq_member(cur_ec, child_expr,
new_relids, new_nullable_relids,
true, cur_em->em_datatype);
cur_em, cur_em->em_datatype);
}
}
}
@ -3204,6 +3268,12 @@ get_eclass_indexes_for_relids(PlannerInfo *root, Relids relids)
{
RelOptInfo *rel = root->simple_rel_array[i];
if (rel == NULL) /* must be an outer join */
{
Assert(bms_is_member(i, root->outer_join_rels));
continue;
}
ec_indexes = bms_add_members(ec_indexes, rel->eclass_indexes);
}
return ec_indexes;

View File

@ -3352,13 +3352,13 @@ check_index_predicates(PlannerInfo *root, RelOptInfo *rel)
* Add on any equivalence-derivable join clauses. Computing the correct
* relid sets for generate_join_implied_equalities is slightly tricky
* because the rel could be a child rel rather than a true baserel, and in
* that case we must remove its parents' relid(s) from all_baserels.
* that case we must subtract its parents' relid(s) from all_query_rels.
*/
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL)
otherrels = bms_difference(root->all_baserels,
otherrels = bms_difference(root->all_query_rels,
find_childrel_parents(root, rel));
else
otherrels = bms_difference(root->all_baserels, rel->relids);
otherrels = bms_difference(root->all_query_rels, rel->relids);
if (!bms_is_empty(otherrels))
clauselist =
@ -3736,7 +3736,8 @@ match_index_to_operand(Node *operand,
*/
if (operand && IsA(operand, Var) &&
index->rel->relid == ((Var *) operand)->varno &&
indkey == ((Var *) operand)->varattno)
indkey == ((Var *) operand)->varattno &&
((Var *) operand)->varnullingrels == NULL)
return true;
}
else

View File

@ -234,7 +234,9 @@ add_paths_to_joinrel(PlannerInfo *root,
* reduces the number of parameterized paths we have to deal with at
* higher join levels, without compromising the quality of the resulting
* plan. We express the restriction as a Relids set that must overlap the
* parameterization of any proposed join path.
* parameterization of any proposed join path. Note: param_source_rels
* should contain only baserels, not OJ relids, so starting from
* all_baserels not all_query_rels is correct.
*/
foreach(lc, root->join_info_list)
{
@ -365,6 +367,60 @@ allow_star_schema_join(PlannerInfo *root,
bms_nonempty_difference(inner_paramrels, outerrelids));
}
/*
* If the parameterization is only partly satisfied by the outer rel,
* the unsatisfied part can't include any outer-join relids that could
* null rels of the satisfied part. That would imply that we're trying
* to use a clause involving a Var with nonempty varnullingrels at
* a join level where that value isn't yet computable.
*
* In practice, this test never finds a problem because earlier join order
* restrictions prevent us from attempting a join that would cause a problem.
* (That's unsurprising, because the code worked before we ever added
* outer-join relids to expression relids.) It still seems worth checking
* as a backstop, but we don't go to a lot of trouble: just reject if the
* unsatisfied part includes any outer-join relids at all.
*/
static inline bool
have_unsafe_outer_join_ref(PlannerInfo *root,
Relids outerrelids,
Relids inner_paramrels)
{
bool result = false;
Relids unsatisfied = bms_difference(inner_paramrels, outerrelids);
if (unlikely(bms_overlap(unsatisfied, root->outer_join_rels)))
{
#ifdef NOT_USED
/* If we ever weaken the join order restrictions, we might need this */
ListCell *lc;
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
if (!bms_is_member(sjinfo->ojrelid, unsatisfied))
continue; /* not relevant */
if (bms_overlap(inner_paramrels, sjinfo->min_righthand) ||
(sjinfo->jointype == JOIN_FULL &&
bms_overlap(inner_paramrels, sjinfo->min_lefthand)))
{
result = true; /* doesn't work */
break;
}
}
#else
/* For now, if we do see an overlap, just assume it's trouble */
result = true;
#endif
}
/* Waste no memory when we reject a path here */
bms_free(unsatisfied);
return result;
}
/*
* paraminfo_get_equal_hashops
* Determine if param_info and innerrel's lateral_vars can be hashed.
@ -657,15 +713,16 @@ try_nestloop_path(PlannerInfo *root,
/*
* Check to see if proposed path is still parameterized, and reject if the
* parameterization wouldn't be sensible --- unless allow_star_schema_join
* says to allow it anyway. Also, we must reject if have_dangerous_phv
* doesn't like the look of it, which could only happen if the nestloop is
* still parameterized.
* says to allow it anyway. Also, we must reject if either
* have_unsafe_outer_join_ref or have_dangerous_phv don't like the look of
* it, which could only happen if the nestloop is still parameterized.
*/
required_outer = calc_nestloop_required_outer(outerrelids, outer_paramrels,
innerrelids, inner_paramrels);
if (required_outer &&
((!bms_overlap(required_outer, extra->param_source_rels) &&
!allow_star_schema_join(root, outerrelids, inner_paramrels)) ||
have_unsafe_outer_join_ref(root, outerrelids, inner_paramrels) ||
have_dangerous_phv(root, outerrelids, inner_paramrels)))
{
/* Waste no memory when we reject a path here */

View File

@ -353,7 +353,10 @@ make_rels_by_clauseless_joins(PlannerInfo *root,
*
* Caller must supply not only the two rels, but the union of their relids.
* (We could simplify the API by computing joinrelids locally, but this
* would be redundant work in the normal path through make_join_rel.)
* would be redundant work in the normal path through make_join_rel.
* Note that this value does NOT include the RT index of any outer join that
* might need to be performed here, so it's not the canonical identifier
* of the join relation.)
*
* On success, *sjinfo_p is set to NULL if this is to be a plain inner join,
* else it's set to point to the associated SpecialJoinInfo node. Also,
@ -695,7 +698,7 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
/* We should never try to join two overlapping sets of rels. */
Assert(!bms_overlap(rel1->relids, rel2->relids));
/* Construct Relids set that identifies the joinrel. */
/* Construct Relids set that identifies the joinrel (without OJ as yet). */
joinrelids = bms_union(rel1->relids, rel2->relids);
/* Check validity and determine join type. */
@ -707,6 +710,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
return NULL;
}
/* If we have an outer join, add its RTI to form the canonical relids. */
if (sjinfo && sjinfo->ojrelid != 0)
joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
/* Swap rels if needed to match the join info. */
if (reversed)
{
@ -730,6 +737,10 @@ make_join_rel(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2)
sjinfo->syn_lefthand = rel1->relids;
sjinfo->syn_righthand = rel2->relids;
sjinfo->jointype = JOIN_INNER;
sjinfo->ojrelid = 0;
sjinfo->commute_above_l = NULL;
sjinfo->commute_above_r = NULL;
sjinfo->commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo->lhs_strict = false;
sjinfo->delay_upper_joins = false;
@ -1510,8 +1521,6 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
/* We should never try to join two overlapping sets of rels. */
Assert(!bms_overlap(child_rel1->relids, child_rel2->relids));
child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos);
/*
* Construct SpecialJoinInfo from parent join relations's
@ -1521,6 +1530,15 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
child_rel1->relids,
child_rel2->relids);
/* Build correct join relids for child join */
child_joinrelids = bms_union(child_rel1->relids, child_rel2->relids);
if (child_sjinfo->ojrelid != 0)
child_joinrelids = bms_add_member(child_joinrelids,
child_sjinfo->ojrelid);
/* Find the AppendRelInfo structures */
appinfos = find_appinfos_by_relids(root, child_joinrelids, &nappinfos);
/*
* Construct restrictions applicable to the child join from those
* applicable to the parent join.
@ -1536,8 +1554,7 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2,
{
child_joinrel = build_child_join_rel(root, child_rel1, child_rel2,
joinrel, child_restrictlist,
child_sjinfo,
child_sjinfo->jointype);
child_sjinfo);
joinrel->part_rels[cnt_parts] = child_joinrel;
joinrel->live_parts = bms_add_member(joinrel->live_parts, cnt_parts);
joinrel->all_partrels = bms_add_members(joinrel->all_partrels,
@ -1583,6 +1600,7 @@ build_child_join_sjinfo(PlannerInfo *root, SpecialJoinInfo *parent_sjinfo,
sjinfo->syn_righthand = adjust_child_relids(sjinfo->syn_righthand,
right_nappinfos,
right_appinfos);
/* outer-join relids need no adjustment */
sjinfo->semi_rhs_exprs = (List *) adjust_appendrel_attrs(root,
(Node *) sjinfo->semi_rhs_exprs,
right_nappinfos,

View File

@ -59,6 +59,7 @@ IsCTIDVar(Var *var, RelOptInfo *rel)
if (var->varattno == SelfItemPointerAttributeNumber &&
var->vartype == TIDOID &&
var->varno == rel->relid &&
var->varnullingrels == NULL &&
var->varlevelsup == 0)
return true;
return false;

View File

@ -34,7 +34,7 @@
/* local functions */
static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo);
static void remove_rel_from_query(PlannerInfo *root, int relid,
static void remove_rel_from_query(PlannerInfo *root, int relid, int ojrelid,
Relids joinrelids);
static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved);
static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel);
@ -70,6 +70,7 @@ restart:
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
Relids joinrelids;
int innerrelid;
int nremoved;
@ -84,9 +85,12 @@ restart:
*/
innerrelid = bms_singleton_member(sjinfo->min_righthand);
remove_rel_from_query(root, innerrelid,
bms_union(sjinfo->min_lefthand,
sjinfo->min_righthand));
/* Compute the relid set for the join we are considering */
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
if (sjinfo->ojrelid != 0)
joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
remove_rel_from_query(root, innerrelid, sjinfo->ojrelid, joinrelids);
/* We verify that exactly one reference gets removed from joinlist */
nremoved = 0;
@ -188,6 +192,8 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
/* Compute the relid set for the join we are considering */
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
if (sjinfo->ojrelid != 0)
joinrelids = bms_add_member(joinrelids, sjinfo->ojrelid);
/*
* We can't remove the join if any inner-rel attributes are used above the
@ -247,6 +253,17 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
/*
* If the current join commutes with some other outer join(s) via
* outer join identity 3, there will be multiple clones of its join
* clauses in the joininfo list. We want to consider only the
* has_clone form of such clauses. Processing more than one form
* would be wasteful, and also some of the others would confuse the
* RINFO_IS_PUSHED_DOWN test below.
*/
if (restrictinfo->is_clone)
continue; /* ignore it */
/*
* If it's not a join clause for this outer join, we can't use it.
* Note that if the clause is pushed-down, then it is logically from
@ -306,10 +323,12 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
* no longer treated as a baserel, and that attributes of other baserels
* are no longer marked as being needed at joins involving this rel.
* Also, join quals involving the rel have to be removed from the joininfo
* lists, but only if they belong to the outer join identified by joinrelids.
* lists, but only if they belong to the outer join identified by ojrelid
* and joinrelids.
*/
static void
remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
remove_rel_from_query(PlannerInfo *root, int relid, int ojrelid,
Relids joinrelids)
{
RelOptInfo *rel = find_base_rel(root, relid);
List *joininfos;
@ -346,9 +365,19 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
{
otherrel->attr_needed[attroff] =
bms_del_member(otherrel->attr_needed[attroff], relid);
otherrel->attr_needed[attroff] =
bms_del_member(otherrel->attr_needed[attroff], ojrelid);
}
}
/*
* Update all_baserels and related relid sets.
*/
root->all_baserels = bms_del_member(root->all_baserels, relid);
root->outer_join_rels = bms_del_member(root->outer_join_rels, ojrelid);
root->all_query_rels = bms_del_member(root->all_query_rels, relid);
root->all_query_rels = bms_del_member(root->all_query_rels, ojrelid);
/*
* Likewise remove references from SpecialJoinInfo data structures.
*
@ -365,6 +394,14 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
sjinfo->min_righthand = bms_del_member(sjinfo->min_righthand, relid);
sjinfo->syn_lefthand = bms_del_member(sjinfo->syn_lefthand, relid);
sjinfo->syn_righthand = bms_del_member(sjinfo->syn_righthand, relid);
sjinfo->min_lefthand = bms_del_member(sjinfo->min_lefthand, ojrelid);
sjinfo->min_righthand = bms_del_member(sjinfo->min_righthand, ojrelid);
sjinfo->syn_lefthand = bms_del_member(sjinfo->syn_lefthand, ojrelid);
sjinfo->syn_righthand = bms_del_member(sjinfo->syn_righthand, ojrelid);
/* relid cannot appear in these fields, but ojrelid can: */
sjinfo->commute_above_l = bms_del_member(sjinfo->commute_above_l, ojrelid);
sjinfo->commute_above_r = bms_del_member(sjinfo->commute_above_r, ojrelid);
sjinfo->commute_below = bms_del_member(sjinfo->commute_below, ojrelid);
}
/*
@ -396,8 +433,10 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
else
{
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, ojrelid);
Assert(!bms_is_empty(phinfo->ph_eval_at));
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
phinfo->ph_needed = bms_del_member(phinfo->ph_needed, ojrelid);
}
}
@ -422,7 +461,12 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
remove_join_clause_from_rels(root, rinfo, rinfo->required_relids);
if (RINFO_IS_PUSHED_DOWN(rinfo, joinrelids))
/*
* If the qual lists ojrelid in its required_relids, it must have come
* from above the outer join we're removing (so we need to keep it);
* if it does not, then it didn't and we can discard it.
*/
if (bms_is_member(ojrelid, rinfo->required_relids))
{
/* Recheck that qual doesn't actually reference the target rel */
Assert(!bms_is_member(relid, rinfo->clause_relids));
@ -434,6 +478,8 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids)
rinfo->required_relids = bms_copy(rinfo->required_relids);
rinfo->required_relids = bms_del_member(rinfo->required_relids,
relid);
rinfo->required_relids = bms_del_member(rinfo->required_relids,
ojrelid);
distribute_restrictinfo_to_rels(root, rinfo);
}
}
@ -548,6 +594,7 @@ reduce_unique_semijoins(PlannerInfo *root)
/* Compute the relid set for the join we are considering */
joinrelids = bms_union(sjinfo->min_lefthand, sjinfo->min_righthand);
Assert(sjinfo->ojrelid == 0); /* SEMI joins don't have RT indexes */
/*
* Since we're only considering a single-rel RHS, any join clauses it

View File

@ -4158,14 +4158,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
/*
* Likewise, copy the relids that are represented by this foreign scan. An
* upper rel doesn't have relids set, but it covers all the base relations
* participating in the underlying scan, so use root's all_baserels.
* upper rel doesn't have relids set, but it covers all the relations
* participating in the underlying scan/join, so use root->all_query_rels.
*/
if (rel->reloptkind == RELOPT_UPPER_REL)
scan_plan->fs_relids = root->all_baserels;
scan_plan->fs_relids = root->all_query_rels;
else
scan_plan->fs_relids = best_path->path.parent->relids;
/*
* Join relid sets include relevant outer joins, but FDWs may need to know
* which are the included base rels. That's a bit tedious to get without
* access to the plan-time data structures, so compute it here.
*/
scan_plan->fs_base_relids = bms_difference(scan_plan->fs_relids,
root->outer_join_rels);
/*
* If this is a foreign join, and to make it valid to push down we had to
* assume that the current user is the same as some user explicitly named
@ -5806,8 +5814,9 @@ make_foreignscan(List *qptlist,
node->fdw_private = fdw_private;
node->fdw_scan_tlist = fdw_scan_tlist;
node->fdw_recheck_quals = fdw_recheck_quals;
/* fs_relids will be filled in by create_foreignscan_plan */
/* fs_relids, fs_base_relids will be filled by create_foreignscan_plan */
node->fs_relids = NULL;
node->fs_base_relids = NULL;
/* fsSystemCol will be filled in by create_foreignscan_plan */
node->fsSystemCol = false;

File diff suppressed because it is too large Load Diff

View File

@ -627,6 +627,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
root->multiexpr_params = NIL;
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 */
@ -912,7 +913,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
*/
if (rte->lateral && root->hasJoinRTEs)
rte->subquery = (Query *)
flatten_join_alias_vars(root->parse,
flatten_join_alias_vars(root, root->parse,
(Node *) rte->subquery);
}
else if (rte->rtekind == RTE_FUNCTION)
@ -1110,7 +1111,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind)
kind == EXPRKIND_VALUES ||
kind == EXPRKIND_TABLESAMPLE ||
kind == EXPRKIND_TABLEFUNC))
expr = flatten_join_alias_vars(root->parse, expr);
expr = flatten_join_alias_vars(root, root->parse, expr);
/*
* Simplify constant expressions. For function RTEs, this was already
@ -2246,7 +2247,7 @@ preprocess_rowmarks(PlannerInfo *root)
* make a bitmapset of all base rels and then remove the items we don't
* need or have FOR [KEY] UPDATE/SHARE marks for.
*/
rels = get_relids_in_jointree((Node *) parse->jointree, false);
rels = get_relids_in_jointree((Node *) parse->jointree, false, false);
if (parse->resultRelation)
rels = bms_del_member(rels, parse->resultRelation);

View File

@ -30,11 +30,21 @@
#include "utils/syscache.h"
typedef enum
{
NRM_EQUAL, /* expect exact match of nullingrels */
NRM_SUBSET, /* actual Var may have a subset of input */
NRM_SUPERSET /* actual Var may have a superset of input */
} NullingRelsMatch;
typedef struct
{
int varno; /* RT index of Var */
AttrNumber varattno; /* attr number of Var */
AttrNumber resno; /* TLE position of Var */
#ifdef USE_ASSERT_CHECKING
Bitmapset *varnullingrels; /* Var's varnullingrels */
#endif
} tlist_vinfo;
typedef struct
@ -60,6 +70,7 @@ typedef struct
indexed_tlist *inner_itlist;
Index acceptable_rel;
int rtoffset;
NullingRelsMatch nrm_match;
double num_exec;
} fix_join_expr_context;
@ -69,6 +80,7 @@ typedef struct
indexed_tlist *subplan_itlist;
int newvarno;
int rtoffset;
NullingRelsMatch nrm_match;
double num_exec;
} fix_upper_expr_context;
@ -159,7 +171,12 @@ static indexed_tlist *build_tlist_index(List *tlist);
static Var *search_indexed_tlist_for_var(Var *var,
indexed_tlist *itlist,
int newvarno,
int rtoffset);
int rtoffset,
NullingRelsMatch nrm_match);
static Var *search_indexed_tlist_for_phv(PlaceHolderVar *phv,
indexed_tlist *itlist,
int newvarno,
NullingRelsMatch nrm_match);
static Var *search_indexed_tlist_for_non_var(Expr *node,
indexed_tlist *itlist,
int newvarno);
@ -172,14 +189,18 @@ static List *fix_join_expr(PlannerInfo *root,
indexed_tlist *outer_itlist,
indexed_tlist *inner_itlist,
Index acceptable_rel,
int rtoffset, double num_exec);
int rtoffset,
NullingRelsMatch nrm_match,
double num_exec);
static Node *fix_join_expr_mutator(Node *node,
fix_join_expr_context *context);
static Node *fix_upper_expr(PlannerInfo *root,
Node *node,
indexed_tlist *subplan_itlist,
int newvarno,
int rtoffset, double num_exec);
int rtoffset,
NullingRelsMatch nrm_match,
double num_exec);
static Node *fix_upper_expr_mutator(Node *node,
fix_upper_expr_context *context);
static List *set_returning_clause_references(PlannerInfo *root,
@ -1118,13 +1139,13 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_join_expr(root, splan->onConflictSet,
NULL, itlist,
linitial_int(splan->resultRelations),
rtoffset, NUM_EXEC_QUAL(plan));
rtoffset, NRM_EQUAL, NUM_EXEC_QUAL(plan));
splan->onConflictWhere = (Node *)
fix_join_expr(root, (List *) splan->onConflictWhere,
NULL, itlist,
linitial_int(splan->resultRelations),
rtoffset, NUM_EXEC_QUAL(plan));
rtoffset, NRM_EQUAL, NUM_EXEC_QUAL(plan));
pfree(itlist);
@ -1181,6 +1202,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
NULL, itlist,
resultrel,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST(plan));
/* Fix quals too. */
@ -1189,6 +1211,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
NULL, itlist,
resultrel,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL(plan));
}
}
@ -1334,6 +1357,7 @@ set_indexonlyscan_references(PlannerInfo *root,
index_itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST((Plan *) plan));
plan->scan.plan.qual = (List *)
fix_upper_expr(root,
@ -1341,6 +1365,7 @@ set_indexonlyscan_references(PlannerInfo *root,
index_itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) plan));
plan->recheckqual = (List *)
fix_upper_expr(root,
@ -1348,6 +1373,7 @@ set_indexonlyscan_references(PlannerInfo *root,
index_itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) plan));
/* indexqual is already transformed to reference index columns */
plan->indexqual = fix_scan_list(root, plan->indexqual,
@ -1554,6 +1580,7 @@ set_foreignscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST((Plan *) fscan));
fscan->scan.plan.qual = (List *)
fix_upper_expr(root,
@ -1561,6 +1588,7 @@ set_foreignscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) fscan));
fscan->fdw_exprs = (List *)
fix_upper_expr(root,
@ -1568,6 +1596,7 @@ set_foreignscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) fscan));
fscan->fdw_recheck_quals = (List *)
fix_upper_expr(root,
@ -1575,6 +1604,7 @@ set_foreignscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) fscan));
pfree(itlist);
/* fdw_scan_tlist itself just needs fix_scan_list() adjustments */
@ -1603,6 +1633,7 @@ set_foreignscan_references(PlannerInfo *root,
}
fscan->fs_relids = offset_relid_set(fscan->fs_relids, rtoffset);
fscan->fs_base_relids = offset_relid_set(fscan->fs_base_relids, rtoffset);
/* Adjust resultRelation if it's valid */
if (fscan->resultRelation > 0)
@ -1635,6 +1666,7 @@ set_customscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST((Plan *) cscan));
cscan->scan.plan.qual = (List *)
fix_upper_expr(root,
@ -1642,6 +1674,7 @@ set_customscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) cscan));
cscan->custom_exprs = (List *)
fix_upper_expr(root,
@ -1649,6 +1682,7 @@ set_customscan_references(PlannerInfo *root,
itlist,
INDEX_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) cscan));
pfree(itlist);
/* custom_scan_tlist itself just needs fix_scan_list() adjustments */
@ -1835,6 +1869,7 @@ set_hash_references(PlannerInfo *root, Plan *plan, int rtoffset)
outer_itlist,
OUTER_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL(plan));
/* Hash doesn't project */
@ -2170,6 +2205,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
/* At scan level, we should always just evaluate the contained expr */
PlaceHolderVar *phv = (PlaceHolderVar *) node;
Assert(phv->phnullingrels == NULL);
return fix_scan_expr_mutator((Node *) phv->phexpr, context);
}
if (IsA(node, AlternativeSubPlan))
@ -2227,6 +2263,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
inner_itlist,
(Index) 0,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) join));
/* Now do join-type-specific stuff */
@ -2239,11 +2276,21 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
{
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
/*
* Because we don't reparameterize parameterized paths to match
* the outer-join level at which they are used, Vars seen in the
* NestLoopParam expression may have nullingrels that are just a
* subset of those in the Vars actually available from the outer
* side. Not checking this exactly is a bit grotty, but the work
* needed to make things match up perfectly seems well out of
* proportion to the value.
*/
nlp->paramval = (Var *) fix_upper_expr(root,
(Node *) nlp->paramval,
outer_itlist,
OUTER_VAR,
rtoffset,
NRM_SUBSET,
NUM_EXEC_TLIST(outer_plan));
/* Check we replaced any PlaceHolderVar with simple Var */
if (!(IsA(nlp->paramval, Var) &&
@ -2261,6 +2308,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
inner_itlist,
(Index) 0,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) join));
}
else if (IsA(join, HashJoin))
@ -2273,6 +2321,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
inner_itlist,
(Index) 0,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) join));
/*
@ -2284,45 +2333,27 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
outer_itlist,
OUTER_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL((Plan *) join));
}
/*
* Now we need to fix up the targetlist and qpqual, which are logically
* above the join. This means they should not re-use any input expression
* that was computed in the nullable side of an outer join. Vars and
* PlaceHolderVars are fine, so we can implement this restriction just by
* clearing has_non_vars in the indexed_tlist structs.
*
* XXX This is a grotty workaround for the fact that we don't clearly
* distinguish between a Var appearing below an outer join and the "same"
* Var appearing above it. If we did, we'd not need to hack the matching
* rules this way.
* above the join. This means that, if it's not an inner join, any Vars
* and PHVs appearing here should have nullingrels that include the
* effects of the outer join, ie they will have nullingrels equal to the
* input Vars' nullingrels plus the bit added by the outer join. We don't
* currently have enough info available here to identify what that should
* be, so we just tell fix_join_expr to accept superset nullingrels
* matches instead of exact ones.
*/
switch (join->jointype)
{
case JOIN_LEFT:
case JOIN_SEMI:
case JOIN_ANTI:
inner_itlist->has_non_vars = false;
break;
case JOIN_RIGHT:
outer_itlist->has_non_vars = false;
break;
case JOIN_FULL:
outer_itlist->has_non_vars = false;
inner_itlist->has_non_vars = false;
break;
default:
break;
}
join->plan.targetlist = fix_join_expr(root,
join->plan.targetlist,
outer_itlist,
inner_itlist,
(Index) 0,
rtoffset,
(join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET),
NUM_EXEC_TLIST((Plan *) join));
join->plan.qual = fix_join_expr(root,
join->plan.qual,
@ -2330,6 +2361,7 @@ set_join_references(PlannerInfo *root, Join *join, int rtoffset)
inner_itlist,
(Index) 0,
rtoffset,
(join->jointype == JOIN_INNER ? NRM_EQUAL : NRM_SUPERSET),
NUM_EXEC_QUAL((Plan *) join));
pfree(outer_itlist);
@ -2384,6 +2416,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
subplan_itlist,
OUTER_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST(plan));
}
else
@ -2392,6 +2425,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
subplan_itlist,
OUTER_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST(plan));
tle = flatCopyTargetEntry(tle);
tle->expr = (Expr *) newexpr;
@ -2405,6 +2439,7 @@ set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
subplan_itlist,
OUTER_VAR,
rtoffset,
NRM_EQUAL,
NUM_EXEC_QUAL(plan));
pfree(subplan_itlist);
@ -2605,7 +2640,7 @@ set_dummy_tlist_references(Plan *plan, int rtoffset)
* tlist_member() searches.
*
* The result of this function is an indexed_tlist struct to pass to
* search_indexed_tlist_for_var() or search_indexed_tlist_for_non_var().
* search_indexed_tlist_for_var() and siblings.
* When done, the indexed_tlist may be freed with a single pfree().
*/
static indexed_tlist *
@ -2637,6 +2672,9 @@ build_tlist_index(List *tlist)
vinfo->varno = var->varno;
vinfo->varattno = var->varattno;
vinfo->resno = tle->resno;
#ifdef USE_ASSERT_CHECKING
vinfo->varnullingrels = var->varnullingrels;
#endif
vinfo++;
}
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
@ -2689,6 +2727,9 @@ build_tlist_index_other_vars(List *tlist, int ignore_rel)
vinfo->varno = var->varno;
vinfo->varattno = var->varattno;
vinfo->resno = tle->resno;
#ifdef USE_ASSERT_CHECKING
vinfo->varnullingrels = var->varnullingrels;
#endif
vinfo++;
}
}
@ -2708,10 +2749,17 @@ build_tlist_index_other_vars(List *tlist, int ignore_rel)
* modified varno/varattno (to wit, newvarno and the resno of the TLE entry).
* Also ensure that varnosyn is incremented by rtoffset.
* If no match, return NULL.
*
* In debugging builds, we cross-check the varnullingrels of the subplan
* output Var based on nrm_match. Most call sites should pass NRM_EQUAL
* indicating we expect an exact match. However, there are places where
* we haven't cleaned things up completely, and we have to settle for
* allowing subset or superset matches.
*/
static Var *
search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
int newvarno, int rtoffset)
int newvarno, int rtoffset,
NullingRelsMatch nrm_match)
{
int varno = var->varno;
AttrNumber varattno = var->varattno;
@ -2727,6 +2775,27 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
/* Found a match */
Var *newvar = copyVar(var);
/*
* Assert that we kept all the nullingrels machinations straight.
*
* XXX we skip the check for system columns and whole-row Vars.
* That's because such Vars might be row identity Vars, which are
* generated without any varnullingrels. It'd be hard to do
* otherwise, since they're normally made very early in planning,
* when we haven't looked at the jointree yet and don't know which
* joins might null such Vars. Doesn't seem worth the expense to
* make them fully valid. (While it's slightly annoying that we
* thereby lose checking for user-written references to such
* columns, it seems unlikely that a bug in nullingrels logic
* would affect only system columns.)
*/
Assert(varattno <= 0 ||
(nrm_match == NRM_SUBSET ?
bms_is_subset(var->varnullingrels, vinfo->varnullingrels) :
nrm_match == NRM_SUPERSET ?
bms_is_subset(vinfo->varnullingrels, var->varnullingrels) :
bms_equal(vinfo->varnullingrels, var->varnullingrels)));
newvar->varno = newvarno;
newvar->varattno = vinfo->resno;
if (newvar->varnosyn > 0)
@ -2739,15 +2808,63 @@ search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
}
/*
* search_indexed_tlist_for_non_var --- find a non-Var in an indexed tlist
* search_indexed_tlist_for_phv --- find a PlaceHolderVar in an indexed tlist
*
* If a match is found, return a Var constructed to reference the tlist item.
* If no match, return NULL.
*
* NOTE: it is a waste of time to call this unless itlist->has_ph_vars or
* itlist->has_non_vars. Furthermore, set_join_references() relies on being
* able to prevent matching of non-Vars by clearing itlist->has_non_vars,
* so there's a correctness reason not to call it unless that's set.
* Cross-check phnullingrels as in search_indexed_tlist_for_var.
*
* NOTE: it is a waste of time to call this unless itlist->has_ph_vars.
*/
static Var *
search_indexed_tlist_for_phv(PlaceHolderVar *phv,
indexed_tlist *itlist, int newvarno,
NullingRelsMatch nrm_match)
{
ListCell *lc;
foreach(lc, itlist->tlist)
{
TargetEntry *tle = (TargetEntry *) lfirst(lc);
if (tle->expr && IsA(tle->expr, PlaceHolderVar))
{
PlaceHolderVar *subphv = (PlaceHolderVar *) tle->expr;
Var *newvar;
/*
* Analogously to search_indexed_tlist_for_var, we match on phid
* only. We don't use equal(), partially for speed but mostly
* because phnullingrels might not be exactly equal.
*/
if (phv->phid != subphv->phid)
continue;
/* Assert that we kept all the nullingrels machinations straight */
Assert(nrm_match == NRM_SUBSET ?
bms_is_subset(phv->phnullingrels, subphv->phnullingrels) :
nrm_match == NRM_SUPERSET ?
bms_is_subset(subphv->phnullingrels, phv->phnullingrels) :
bms_equal(subphv->phnullingrels, phv->phnullingrels));
/* Found a matching subplan output expression */
newvar = makeVarFromTargetEntry(newvarno, tle);
newvar->varnosyn = 0; /* wasn't ever a plain Var */
newvar->varattnosyn = 0;
return newvar;
}
}
return NULL; /* no match */
}
/*
* search_indexed_tlist_for_non_var --- find a non-Var/PHV in an indexed tlist
*
* If a match is found, return a Var constructed to reference the tlist item.
* If no match, return NULL.
*
* NOTE: it is a waste of time to call this unless itlist->has_non_vars.
*/
static Var *
search_indexed_tlist_for_non_var(Expr *node,
@ -2854,6 +2971,7 @@ search_indexed_tlist_for_sortgroupref(Expr *node,
* 'acceptable_rel' is either zero or the rangetable index of a relation
* whose Vars may appear in the clause without provoking an error
* 'rtoffset': how much to increment varnos by
* 'nrm_match': as for search_indexed_tlist_for_var()
* 'num_exec': estimated number of executions of expression
*
* Returns the new expression tree. The original clause structure is
@ -2866,6 +2984,7 @@ fix_join_expr(PlannerInfo *root,
indexed_tlist *inner_itlist,
Index acceptable_rel,
int rtoffset,
NullingRelsMatch nrm_match,
double num_exec)
{
fix_join_expr_context context;
@ -2875,6 +2994,7 @@ fix_join_expr(PlannerInfo *root,
context.inner_itlist = inner_itlist;
context.acceptable_rel = acceptable_rel;
context.rtoffset = rtoffset;
context.nrm_match = nrm_match;
context.num_exec = num_exec;
return (List *) fix_join_expr_mutator((Node *) clauses, &context);
}
@ -2896,7 +3016,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
newvar = search_indexed_tlist_for_var(var,
context->outer_itlist,
OUTER_VAR,
context->rtoffset);
context->rtoffset,
context->nrm_match);
if (newvar)
return (Node *) newvar;
}
@ -2907,7 +3028,8 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
newvar = search_indexed_tlist_for_var(var,
context->inner_itlist,
INNER_VAR,
context->rtoffset);
context->rtoffset,
context->nrm_match);
if (newvar)
return (Node *) newvar;
}
@ -2932,22 +3054,25 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->outer_itlist && context->outer_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) phv,
context->outer_itlist,
OUTER_VAR);
newvar = search_indexed_tlist_for_phv(phv,
context->outer_itlist,
OUTER_VAR,
context->nrm_match);
if (newvar)
return (Node *) newvar;
}
if (context->inner_itlist && context->inner_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) phv,
context->inner_itlist,
INNER_VAR);
newvar = search_indexed_tlist_for_phv(phv,
context->inner_itlist,
INNER_VAR,
context->nrm_match);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plans, evaluate the contained expr */
/* XXX can we assert something about phnullingrels? */
return fix_join_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlists have any */
@ -3006,6 +3131,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
* 'subplan_itlist': indexed target list for subplan (or index)
* 'newvarno': varno to use for Vars referencing tlist elements
* 'rtoffset': how much to increment varnos by
* 'nrm_match': as for search_indexed_tlist_for_var()
* 'num_exec': estimated number of executions of expression
*
* The resulting tree is a copy of the original in which all Var nodes have
@ -3018,6 +3144,7 @@ fix_upper_expr(PlannerInfo *root,
indexed_tlist *subplan_itlist,
int newvarno,
int rtoffset,
NullingRelsMatch nrm_match,
double num_exec)
{
fix_upper_expr_context context;
@ -3026,6 +3153,7 @@ fix_upper_expr(PlannerInfo *root,
context.subplan_itlist = subplan_itlist;
context.newvarno = newvarno;
context.rtoffset = rtoffset;
context.nrm_match = nrm_match;
context.num_exec = num_exec;
return fix_upper_expr_mutator(node, &context);
}
@ -3044,7 +3172,8 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
newvar = search_indexed_tlist_for_var(var,
context->subplan_itlist,
context->newvarno,
context->rtoffset);
context->rtoffset,
context->nrm_match);
if (!newvar)
elog(ERROR, "variable not found in subplan target list");
return (Node *) newvar;
@ -3056,13 +3185,15 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
if (context->subplan_itlist->has_ph_vars)
{
newvar = search_indexed_tlist_for_non_var((Expr *) phv,
context->subplan_itlist,
context->newvarno);
newvar = search_indexed_tlist_for_phv(phv,
context->subplan_itlist,
context->newvarno,
context->nrm_match);
if (newvar)
return (Node *) newvar;
}
/* If not supplied by input plan, evaluate the contained expr */
/* XXX can we assert something about phnullingrels? */
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
}
/* Try matching more complex expressions too, if tlist has any */
@ -3169,6 +3300,7 @@ set_returning_clause_references(PlannerInfo *root,
NULL,
resultRelation,
rtoffset,
NRM_EQUAL,
NUM_EXEC_TLIST(topplan));
pfree(itlist);

File diff suppressed because it is too large Load Diff

View File

@ -228,6 +228,28 @@ adjust_appendrel_attrs_mutator(Node *node,
if (var->varlevelsup != 0)
return (Node *) var; /* no changes needed */
/*
* You might think we need to adjust var->varnullingrels, but that
* shouldn't need any changes. It will contain outer-join relids,
* while the transformation we are making affects only baserels.
* Below, we just propagate var->varnullingrels into the translated
* Var.
*
* If var->varnullingrels isn't empty, and the translation wouldn't be
* a Var, we have to fail. One could imagine wrapping the translated
* expression in a PlaceHolderVar, but that won't work because this is
* typically used after freezing placeholders. Fortunately, the case
* appears unreachable at the moment. We can see nonempty
* var->varnullingrels here, but only in cases involving partitionwise
* joining, and in such cases the translations will always be Vars.
* (Non-Var translations occur only for appendrels made by flattening
* UNION ALL subqueries.) Should we need to make this work in future,
* a possible fix is to mandate that prepjointree.c create PHVs for
* all non-Var outputs of such subqueries, and then we could look up
* the pre-existing PHV here. Or perhaps just wrap the translations
* that way to begin with?
*/
for (cnt = 0; cnt < nappinfos; cnt++)
{
if (var->varno == appinfos[cnt]->parent_relid)
@ -255,6 +277,10 @@ adjust_appendrel_attrs_mutator(Node *node,
if (newnode == NULL)
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
var->varattno, get_rel_name(appinfo->parent_reloid));
if (IsA(newnode, Var))
((Var *) newnode)->varnullingrels = var->varnullingrels;
else if (var->varnullingrels != NULL)
elog(ERROR, "failed to apply nullingrels to a non-Var");
return newnode;
}
else if (var->varattno == 0)
@ -308,6 +334,9 @@ adjust_appendrel_attrs_mutator(Node *node,
rowexpr->colnames = copyObject(rte->eref->colnames);
rowexpr->location = -1;
if (var->varnullingrels != NULL)
elog(ERROR, "failed to apply nullingrels to a non-Var");
return (Node *) rowexpr;
}
}
@ -348,6 +377,8 @@ adjust_appendrel_attrs_mutator(Node *node,
var = copyObject(ridinfo->rowidvar);
/* ... but use the correct relid */
var->varno = leaf_relid;
/* identity vars shouldn't have nulling rels */
Assert(var->varnullingrels == NULL);
/* varnosyn in the RowIdentityVarInfo is probably wrong */
var->varnosyn = 0;
var->varattnosyn = 0;
@ -392,8 +423,11 @@ adjust_appendrel_attrs_mutator(Node *node,
(void *) context);
/* now fix PlaceHolderVar's relid sets */
if (phv->phlevelsup == 0)
phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos,
context->appinfos);
{
phv->phrels = adjust_child_relids(phv->phrels,
nappinfos, appinfos);
/* as above, we needn't touch phnullingrels */
}
return (Node *) phv;
}
/* Shouldn't need to handle planner auxiliary nodes here */
@ -412,7 +446,7 @@ adjust_appendrel_attrs_mutator(Node *node,
RestrictInfo *oldinfo = (RestrictInfo *) node;
RestrictInfo *newinfo = makeNode(RestrictInfo);
/* Copy all flat-copiable fields */
/* Copy all flat-copiable fields, notably including rinfo_serial */
memcpy(newinfo, oldinfo, sizeof(RestrictInfo));
/* Recursively fix the clause itself */
@ -688,7 +722,11 @@ get_translated_update_targetlist(PlannerInfo *root, Index relid,
/*
* find_appinfos_by_relids
* Find AppendRelInfo structures for all relations specified by relids.
* Find AppendRelInfo structures for base relations listed in relids.
*
* The relids argument is typically a join relation's relids, which can
* include outer-join RT indexes in addition to baserels. We silently
* ignore the outer joins.
*
* 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.
@ -700,8 +738,9 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
int cnt = 0;
int i;
*nappinfos = bms_num_members(relids);
appinfos = (AppendRelInfo **) palloc(sizeof(AppendRelInfo *) * *nappinfos);
/* Allocate an array that's certainly big enough */
appinfos = (AppendRelInfo **)
palloc(sizeof(AppendRelInfo *) * bms_num_members(relids));
i = -1;
while ((i = bms_next_member(relids, i)) >= 0)
@ -709,10 +748,17 @@ find_appinfos_by_relids(PlannerInfo *root, Relids relids, int *nappinfos)
AppendRelInfo *appinfo = root->append_rel_array[i];
if (!appinfo)
{
/* Probably i is an OJ index, but let's check */
if (find_base_rel_ignore_join(root, i) == NULL)
continue;
/* It's a base rel, but we lack an append_rel_array entry */
elog(ERROR, "child rel %d not found in append_rel_array", i);
}
appinfos[cnt++] = appinfo;
}
*nappinfos = cnt;
return appinfos;
}
@ -754,6 +800,7 @@ add_row_identity_var(PlannerInfo *root, Var *orig_var,
Assert(IsA(orig_var, Var));
Assert(orig_var->varno == rtindex);
Assert(orig_var->varlevelsup == 0);
Assert(orig_var->varnullingrels == NULL);
/*
* If we're doing non-inherited UPDATE/DELETE/MERGE, there's little need

View File

@ -2004,14 +2004,16 @@ is_pseudo_constant_clause_relids(Node *clause, Relids relids)
* NumRelids
* (formerly clause_relids)
*
* Returns the number of different relations referenced in 'clause'.
* Returns the number of different base relations referenced in 'clause'.
*/
int
NumRelids(PlannerInfo *root, Node *clause)
{
int result;
Relids varnos = pull_varnos(root, clause);
int result = bms_num_members(varnos);
varnos = bms_del_members(varnos, root->outer_join_rels);
result = bms_num_members(varnos);
bms_free(varnos);
return result;
}

View File

@ -88,8 +88,8 @@ have_relevant_joinclause(PlannerInfo *root,
* not depend on context).
*
* 'restrictinfo' describes the join clause
* 'join_relids' is the list of relations participating in the join clause
* (there must be more than one)
* 'join_relids' is the set of relations participating in the join clause
* (some of these could be outer joins)
*/
void
add_join_clause_to_rels(PlannerInfo *root,
@ -101,8 +101,11 @@ add_join_clause_to_rels(PlannerInfo *root,
cur_relid = -1;
while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
{
RelOptInfo *rel = find_base_rel(root, cur_relid);
RelOptInfo *rel = find_base_rel_ignore_join(root, cur_relid);
/* We only need to add the clause to baserels */
if (rel == NULL)
continue;
rel->joininfo = lappend(rel->joininfo, restrictinfo);
}
}
@ -115,8 +118,8 @@ add_join_clause_to_rels(PlannerInfo *root,
* discover that a relation need not be joined at all.
*
* 'restrictinfo' describes the join clause
* 'join_relids' is the list of relations participating in the join clause
* (there must be more than one)
* '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,
@ -128,7 +131,11 @@ remove_join_clause_from_rels(PlannerInfo *root,
cur_relid = -1;
while ((cur_relid = bms_next_member(join_relids, cur_relid)) >= 0)
{
RelOptInfo *rel = find_base_rel(root, cur_relid);
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

View File

@ -338,6 +338,10 @@ consider_new_or_clause(PlannerInfo *root, RelOptInfo *rel,
sjinfo.syn_lefthand = sjinfo.min_lefthand;
sjinfo.syn_righthand = sjinfo.min_righthand;
sjinfo.jointype = JOIN_INNER;
sjinfo.ojrelid = 0;
sjinfo.commute_above_l = NULL;
sjinfo.commute_above_r = NULL;
sjinfo.commute_below = NULL;
/* we don't bother trying to make the remaining fields valid */
sjinfo.lhs_strict = false;
sjinfo.delay_upper_joins = false;

View File

@ -1307,7 +1307,7 @@ create_append_path(PlannerInfo *root,
* Apply query-wide LIMIT if known and path is for sole base relation.
* (Handling this at this low level is a bit klugy.)
*/
if (root != NULL && bms_equal(rel->relids, root->all_baserels))
if (root != NULL && bms_equal(rel->relids, root->all_query_rels))
pathnode->limit_tuples = root->limit_tuples;
else
pathnode->limit_tuples = -1.0;
@ -1436,7 +1436,7 @@ create_merge_append_path(PlannerInfo *root,
* Apply query-wide LIMIT if known and path is for sole base relation.
* (Handling this at this low level is a bit klugy.)
*/
if (bms_equal(rel->relids, root->all_baserels))
if (bms_equal(rel->relids, root->all_query_rels))
pathnode->limit_tuples = root->limit_tuples;
else
pathnode->limit_tuples = -1.0;
@ -2442,12 +2442,12 @@ create_nestloop_path(PlannerInfo *root,
* restrict_clauses that are due to be moved into the inner path. We have
* to do this now, rather than postpone the work till createplan time,
* because the restrict_clauses list can affect the size and cost
* estimates for this path.
* estimates for this path. We detect such clauses by checking for serial
* number match to clauses already enforced in the inner path.
*/
if (bms_overlap(inner_req_outer, outer_path->parent->relids))
{
Relids inner_and_outer = bms_union(inner_path->parent->relids,
inner_req_outer);
Bitmapset *enforced_serials = get_param_path_clause_serials(inner_path);
List *jclauses = NIL;
ListCell *lc;
@ -2455,9 +2455,7 @@ create_nestloop_path(PlannerInfo *root,
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (!join_clause_is_movable_into(rinfo,
inner_path->parent->relids,
inner_and_outer))
if (!bms_is_member(rinfo->rinfo_serial, enforced_serials))
jclauses = lappend(jclauses, rinfo);
}
restrict_clauses = jclauses;
@ -4298,6 +4296,7 @@ do { \
new_ppi->ppi_rows = old_ppi->ppi_rows;
new_ppi->ppi_clauses = old_ppi->ppi_clauses;
ADJUST_CHILD_ATTRS(new_ppi->ppi_clauses);
new_ppi->ppi_serials = bms_copy(old_ppi->ppi_serials);
rel->ppilist = lappend(rel->ppilist, new_ppi);
MemoryContextSwitchTo(oldcontext);

View File

@ -23,17 +23,32 @@
#include "optimizer/planmain.h"
#include "utils/lsyscache.h"
typedef struct contain_placeholder_references_context
{
int relid;
int sublevels_up;
} contain_placeholder_references_context;
/* Local functions */
static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode);
static void find_placeholders_in_expr(PlannerInfo *root, Node *expr);
static bool contain_placeholder_references_walker(Node *node,
contain_placeholder_references_context *context);
/*
* make_placeholder_expr
* Make a PlaceHolderVar for the given expression.
*
* phrels is the syntactic location (as a set of baserels) to attribute
* phrels is the syntactic location (as a set of relids) to attribute
* to the expression.
*
* The caller is responsible for adjusting phlevelsup and phnullingrels
* as needed. Because we do not know here which query level the PHV
* will be associated with, it's important that this function touches
* only root->glob; messing with other parts of PlannerInfo would be
* likely to do the wrong thing.
*/
PlaceHolderVar *
make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
@ -42,8 +57,9 @@ make_placeholder_expr(PlannerInfo *root, Expr *expr, Relids phrels)
phv->phexpr = expr;
phv->phrels = phrels;
phv->phnullingrels = NULL; /* caller may change this later */
phv->phid = ++(root->glob->lastPHId);
phv->phlevelsup = 0;
phv->phlevelsup = 0; /* caller may change this later */
return phv;
}
@ -92,6 +108,15 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv)
phinfo->phid = phv->phid;
phinfo->ph_var = copyObject(phv);
/*
* By convention, phinfo->ph_var->phnullingrels is always empty, since the
* PlaceHolderInfo represents the initially-calculated state of the
* PlaceHolderVar. PlaceHolderVars appearing in the query tree might have
* varying values of phnullingrels, reflecting outer joins applied above
* the calculation level.
*/
phinfo->ph_var->phnullingrels = NULL;
/*
* Any referenced rels that are outside the PHV's syntactic scope are
* LATERAL references, which should be included in ph_lateral but not in
@ -339,6 +364,8 @@ update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo)
sjinfo->min_lefthand);
eval_at = bms_add_members(eval_at,
sjinfo->min_righthand);
if (sjinfo->ojrelid)
eval_at = bms_add_member(eval_at, sjinfo->ojrelid);
/* we'll need another iteration */
found_some = true;
}
@ -413,6 +440,14 @@ add_placeholders_to_base_rels(PlannerInfo *root)
{
RelOptInfo *rel = find_base_rel(root, varno);
/*
* As in add_vars_to_targetlist(), a value computed at scan level
* has not yet been nulled by any outer join, so its phnullingrels
* should be empty.
*/
Assert(phinfo->ph_var->phnullingrels == NULL);
/* Copying the PHV might be unnecessary here, but be safe */
rel->reltarget->exprs = lappend(rel->reltarget->exprs,
copyObject(phinfo->ph_var));
/* reltarget's cost and width fields will be updated later */
@ -435,7 +470,8 @@ add_placeholders_to_base_rels(PlannerInfo *root)
*/
void
add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel)
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
SpecialJoinInfo *sjinfo)
{
Relids relids = joinrel->relids;
ListCell *lc;
@ -466,9 +502,17 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
if (!bms_is_subset(phinfo->ph_eval_at, outer_rel->relids) &&
!bms_is_subset(phinfo->ph_eval_at, inner_rel->relids))
{
PlaceHolderVar *phv = phinfo->ph_var;
/* Copying might be unnecessary here, but be safe */
PlaceHolderVar *phv = copyObject(phinfo->ph_var);
QualCost cost;
/*
* It'll start out not nulled by anything. Joins above
* this one might add to its phnullingrels later, in much
* the same way as for Vars.
*/
Assert(phv->phnullingrels == NULL);
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
phv);
cost_qual_eval_node(&cost, (Node *) phv->phexpr, root);
@ -499,3 +543,74 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
}
}
}
/*
* contain_placeholder_references_to
* Detect whether any PlaceHolderVars in the given clause contain
* references to the given relid (typically an OJ relid).
*
* "Contain" means that there's a use of the relid inside the PHV's
* contained expression, so that changing the nullability status of
* the rel might change what the PHV computes.
*
* The code here to cope with upper-level PHVs is likely dead, but keep it
* anyway just in case.
*/
bool
contain_placeholder_references_to(PlannerInfo *root, Node *clause,
int relid)
{
contain_placeholder_references_context context;
/* We can answer quickly in the common case that there's no PHVs at all */
if (root->glob->lastPHId == 0)
return false;
/* Else run the recursive search */
context.relid = relid;
context.sublevels_up = 0;
return contain_placeholder_references_walker(clause, &context);
}
static bool
contain_placeholder_references_walker(Node *node,
contain_placeholder_references_context *context)
{
if (node == NULL)
return false;
if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
/* We should just look through PHVs of other query levels */
if (phv->phlevelsup == context->sublevels_up)
{
/* If phrels matches, we found what we came for */
if (bms_is_member(context->relid, phv->phrels))
return true;
/*
* We should not examine phnullingrels: what we are looking for is
* references in the contained expression, not OJs that might null
* the result afterwards. Also, we don't need to recurse into the
* contained expression, because phrels should adequately
* summarize what's in there. So we're done here.
*/
return false;
}
}
else if (IsA(node, Query))
{
/* Recurse into RTE subquery or not-yet-planned sublink subquery */
bool result;
context->sublevels_up++;
result = query_tree_walker((Query *) node,
contain_placeholder_references_walker,
context,
0);
context->sublevels_up--;
return result;
}
return expression_tree_walker(node, contain_placeholder_references_walker,
context);
}

View File

@ -28,6 +28,7 @@
#include "optimizer/plancat.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "rewrite/rewriteManip.h"
#include "parser/parse_relation.h"
#include "utils/hsearch.h"
#include "utils/lsyscache.h"
@ -40,7 +41,9 @@ typedef struct JoinHashEntry
} JoinHashEntry;
static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *input_rel);
RelOptInfo *input_rel,
SpecialJoinInfo *sjinfo,
bool can_null);
static List *build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel,
@ -48,8 +51,10 @@ static List *build_joinrel_restrictlist(PlannerInfo *root,
static void build_joinrel_joinlist(RelOptInfo *joinrel,
RelOptInfo *outer_rel,
RelOptInfo *inner_rel);
static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
List *joininfo_list,
static List *subbuild_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *input_rel,
Relids both_input_relids,
List *new_restrictlist);
static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
List *joininfo_list,
@ -57,10 +62,12 @@ static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
static void set_foreign_rel_properties(RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel);
static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
static void build_joinrel_partition_info(RelOptInfo *joinrel,
static void build_joinrel_partition_info(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
List *restrictlist, JoinType jointype);
static bool have_partkey_equi_join(RelOptInfo *joinrel,
SpecialJoinInfo *sjinfo,
List *restrictlist);
static bool have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype, List *restrictlist);
static int match_expr_to_partition_keys(Expr *expr, RelOptInfo *rel,
@ -373,7 +380,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
/*
* find_base_rel
* Find a base or other relation entry, which must already exist.
* Find a base or otherrel relation entry, which must already exist.
*/
RelOptInfo *
find_base_rel(PlannerInfo *root, int relid)
@ -394,6 +401,44 @@ find_base_rel(PlannerInfo *root, int relid)
return NULL; /* keep compiler quiet */
}
/*
* find_base_rel_ignore_join
* Find a base or otherrel relation entry, which must already exist.
*
* Unlike find_base_rel, if relid references an outer join then this
* will return NULL rather than raising an error. This is convenient
* for callers that must deal with relid sets including both base and
* outer joins.
*/
RelOptInfo *
find_base_rel_ignore_join(PlannerInfo *root, int relid)
{
Assert(relid > 0);
if (relid < root->simple_rel_array_size)
{
RelOptInfo *rel;
RangeTblEntry *rte;
rel = root->simple_rel_array[relid];
if (rel)
return rel;
/*
* We could just return NULL here, but for debugging purposes it seems
* best to actually verify that the relid is an outer join and not
* something weird.
*/
rte = root->simple_rte_array[relid];
if (rte && rte->rtekind == RTE_JOIN && rte->jointype != JOIN_INNER)
return NULL;
}
elog(ERROR, "no relation entry for relid %d", relid);
return NULL; /* keep compiler quiet */
}
/*
* build_join_rel_hash
* Construct the auxiliary hash table for join relations.
@ -692,9 +737,11 @@ build_join_rel(PlannerInfo *root,
* and inner rels we first try to build it from. But the contents should
* be the same regardless.
*/
build_joinrel_tlist(root, joinrel, outer_rel);
build_joinrel_tlist(root, joinrel, inner_rel);
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
build_joinrel_tlist(root, joinrel, outer_rel, sjinfo,
(sjinfo->jointype == JOIN_FULL));
build_joinrel_tlist(root, joinrel, inner_rel, sjinfo,
(sjinfo->jointype != JOIN_INNER));
add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel, sjinfo);
/*
* add_placeholders_to_joinrel also took care of adding the ph_lateral
@ -726,8 +773,8 @@ build_join_rel(PlannerInfo *root,
joinrel->has_eclass_joins = has_relevant_eclass_joinclause(root, joinrel);
/* Store the partition information. */
build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
sjinfo->jointype);
build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo,
restrictlist);
/*
* Set estimates of the joinrel's size.
@ -783,16 +830,14 @@ build_join_rel(PlannerInfo *root,
* 'parent_joinrel' is the RelOptInfo representing the join between parent
* relations. Some of the members of new RelOptInfo are produced by
* translating corresponding members of this RelOptInfo
* 'sjinfo': child-join context info
* 'restrictlist': list of RestrictInfo nodes that apply to this particular
* pair of joinable relations
* 'jointype' is the join type (inner, left, full, etc)
* 'sjinfo': child join's join-type details
*/
RelOptInfo *
build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, RelOptInfo *parent_joinrel,
List *restrictlist, SpecialJoinInfo *sjinfo,
JoinType jointype)
List *restrictlist, SpecialJoinInfo *sjinfo)
{
RelOptInfo *joinrel = makeNode(RelOptInfo);
AppendRelInfo **appinfos;
@ -806,6 +851,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->reloptkind = RELOPT_OTHER_JOINREL;
joinrel->relids = bms_union(outer_rel->relids, inner_rel->relids);
if (sjinfo->ojrelid != 0)
joinrel->relids = bms_add_member(joinrel->relids, sjinfo->ojrelid);
joinrel->rows = 0;
/* cheap startup cost is interesting iff not all tuples to be retrieved */
joinrel->consider_startup = (root->tuple_fraction > 0);
@ -892,8 +939,8 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
joinrel->has_eclass_joins = parent_joinrel->has_eclass_joins;
/* Is the join between partitions itself partitioned? */
build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
jointype);
build_joinrel_partition_info(root, joinrel, outer_rel, inner_rel, sjinfo,
restrictlist);
/* Child joinrel is parallel safe if parent is parallel safe. */
joinrel->consider_parallel = parent_joinrel->consider_parallel;
@ -975,10 +1022,41 @@ min_join_parameterization(PlannerInfo *root,
*
* We also compute the expected width of the join's output, making use
* of data that was cached at the baserel level by set_rel_width().
*
* Pass can_null as true if the join is an outer join that can null Vars
* from this input relation. If so, we will (normally) add the join's relid
* to the nulling bitmaps of Vars and PHVs bubbled up from the input.
*
* When forming an outer join's target list, special handling is needed
* in case the outer join was commuted with another one per outer join
* identity 3 (see optimizer/README). We must take steps to ensure that
* the output Vars have the same nulling bitmaps that they would if the
* two joins had been done in syntactic order; else they won't match Vars
* appearing higher in the query tree. We need to do two things:
*
* First, sjinfo->commute_above_r is added to the nulling bitmaps of RHS Vars.
* This takes care of the case where we implement
* A leftjoin (B leftjoin C on (Pbc)) on (Pab)
* as
* (A leftjoin B on (Pab)) leftjoin C on (Pbc)
* The C columns emitted by the B/C join need to be shown as nulled by both
* the B/C and A/B joins, even though they've not traversed the A/B join.
* (If the joins haven't been commuted, we are adding the nullingrel bits
* prematurely; but that's okay because the C columns can't be referenced
* between here and the upper join.)
*
* Second, if a RHS Var has any of the relids in sjinfo->commute_above_l
* already set in its nulling bitmap, then we *don't* add sjinfo->ojrelid
* to its nulling bitmap (but we do still add commute_above_r). This takes
* care of the reverse transformation: if the original syntax was
* (A leftjoin B on (Pab)) leftjoin C on (Pbc)
* then the now-upper A/B join must not mark C columns as nulled by itself.
*/
static void
build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *input_rel)
RelOptInfo *input_rel,
SpecialJoinInfo *sjinfo,
bool can_null)
{
Relids relids = joinrel->relids;
ListCell *vars;
@ -998,7 +1076,24 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
/* Is it still needed above this joinrel? */
if (bms_nonempty_difference(phinfo->ph_needed, relids))
{
/* Yup, add it to the output */
/*
* Yup, add it to the output. If this join potentially nulls
* this input, we have to update the PHV's phnullingrels,
* which means making a copy.
*/
if (can_null)
{
phv = copyObject(phv);
/* See comments above to understand this logic */
if (sjinfo->ojrelid != 0 &&
!bms_overlap(phv->phnullingrels, sjinfo->commute_above_l))
phv->phnullingrels = bms_add_member(phv->phnullingrels,
sjinfo->ojrelid);
if (sjinfo->commute_above_r)
phv->phnullingrels = bms_add_members(phv->phnullingrels,
sjinfo->commute_above_r);
}
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
phv);
/* Bubbling up the precomputed result has cost zero */
@ -1022,9 +1117,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
RowIdentityVarInfo *ridinfo = (RowIdentityVarInfo *)
list_nth(root->row_identity_vars, var->varattno - 1);
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
/* Update reltarget width estimate from RowIdentityVarInfo */
joinrel->reltarget->width += ridinfo->rowidwidth;
}
else
@ -1037,15 +1130,35 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
/* Is it still needed above this joinrel? */
ndx = var->varattno - baserel->min_attr;
if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
{
/* Yup, add it to the output */
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
joinrel->reltarget->width += baserel->attr_widths[ndx];
}
if (!bms_nonempty_difference(baserel->attr_needed[ndx], relids))
continue; /* nope, skip it */
/* Update reltarget width estimate from baserel's attr_widths */
joinrel->reltarget->width += baserel->attr_widths[ndx];
}
/*
* Add the Var to the output. If this join potentially nulls this
* input, we have to update the Var's varnullingrels, which means
* making a copy.
*/
if (can_null)
{
var = copyObject(var);
/* See comments above to understand this logic */
if (sjinfo->ojrelid != 0 &&
!bms_overlap(var->varnullingrels, sjinfo->commute_above_l))
var->varnullingrels = bms_add_member(var->varnullingrels,
sjinfo->ojrelid);
if (sjinfo->commute_above_r)
var->varnullingrels = bms_add_members(var->varnullingrels,
sjinfo->commute_above_r);
}
joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs,
var);
/* Vars have cost zero, so no need to adjust reltarget->cost */
}
}
@ -1064,7 +1177,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
* is not handled in the sub-relations, so it depends on which
* sub-relations are considered.
*
* If a join clause from an input relation refers to base rels still not
* If a join clause from an input relation refers to base+OJ rels still not
* present in the joinrel, then it is still a join clause for the joinrel;
* we put it into the joininfo list for the joinrel. Otherwise,
* the clause is now a restrict clause for the joined relation, and we
@ -1098,14 +1211,19 @@ build_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *inner_rel)
{
List *result;
Relids both_input_relids;
both_input_relids = bms_union(outer_rel->relids, inner_rel->relids);
/*
* Collect all the clauses that syntactically belong at this level,
* eliminating any duplicates (important since we will see many of the
* same clauses arriving from both input relations).
*/
result = subbuild_joinrel_restrictlist(joinrel, outer_rel->joininfo, NIL);
result = subbuild_joinrel_restrictlist(joinrel, inner_rel->joininfo, result);
result = subbuild_joinrel_restrictlist(root, joinrel, outer_rel,
both_input_relids, NIL);
result = subbuild_joinrel_restrictlist(root, joinrel, inner_rel,
both_input_relids, result);
/*
* Add on any clauses derived from EquivalenceClasses. These cannot be
@ -1140,24 +1258,63 @@ build_joinrel_joinlist(RelOptInfo *joinrel,
}
static List *
subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
List *joininfo_list,
subbuild_joinrel_restrictlist(PlannerInfo *root,
RelOptInfo *joinrel,
RelOptInfo *input_rel,
Relids both_input_relids,
List *new_restrictlist)
{
ListCell *l;
foreach(l, joininfo_list)
foreach(l, input_rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
if (bms_is_subset(rinfo->required_relids, joinrel->relids))
{
/*
* This clause becomes a restriction clause for the joinrel, since
* it refers to no outside rels. Add it to the list, being
* careful to eliminate duplicates. (Since RestrictInfo nodes in
* different joinlists will have been multiply-linked rather than
* copied, pointer equality should be a sufficient test.)
* This clause should become a restriction clause for the joinrel,
* since it refers to no outside rels. However, if it's a clone
* clause then it might be too late to evaluate it, so we have to
* check. (If it is too late, just ignore the clause, taking it
* on faith that another clone was or will be selected.) Clone
* clauses should always be outer-join clauses, so we compare
* against both_input_relids.
*/
if (rinfo->has_clone || rinfo->is_clone)
{
Assert(!RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids));
if (!bms_is_subset(rinfo->required_relids, both_input_relids))
continue;
if (!clause_is_computable_at(root, rinfo->clause_relids,
both_input_relids))
continue;
}
else
{
/*
* For non-clone clauses, we just Assert it's OK. These might
* be either join or filter clauses.
*/
#ifdef USE_ASSERT_CHECKING
if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
Assert(clause_is_computable_at(root, rinfo->clause_relids,
joinrel->relids));
else
{
Assert(bms_is_subset(rinfo->required_relids,
both_input_relids));
Assert(clause_is_computable_at(root, rinfo->clause_relids,
both_input_relids));
}
#endif
}
/*
* OK, so add it to the list, being careful to eliminate
* duplicates. (Since RestrictInfo nodes in different joinlists
* will have been multiply-linked rather than copied, pointer
* equality should be a sufficient test.)
*/
new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo);
}
@ -1319,6 +1476,7 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *ppi;
Relids joinrelids;
List *pclauses;
Bitmapset *pserials;
double rows;
ListCell *lc;
@ -1361,6 +1519,15 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
required_outer,
baserel));
/* Compute set of serial numbers of the enforced clauses */
pserials = NULL;
foreach(lc, pclauses)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
pserials = bms_add_member(pserials, rinfo->rinfo_serial);
}
/* Estimate the number of rows returned by the parameterized scan */
rows = get_parameterized_baserel_size(root, baserel, pclauses);
@ -1369,6 +1536,7 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel,
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = pclauses;
ppi->ppi_serials = pserials;
baserel->ppilist = lappend(baserel->ppilist, ppi);
return ppi;
@ -1594,6 +1762,7 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel,
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = rows;
ppi->ppi_clauses = NIL;
ppi->ppi_serials = NULL;
joinrel->ppilist = lappend(joinrel->ppilist, ppi);
return ppi;
@ -1632,6 +1801,7 @@ get_appendrel_parampathinfo(RelOptInfo *appendrel, Relids required_outer)
ppi->ppi_req_outer = required_outer;
ppi->ppi_rows = 0;
ppi->ppi_clauses = NIL;
ppi->ppi_serials = NULL;
appendrel->ppilist = lappend(appendrel->ppilist, ppi);
return ppi;
@ -1657,6 +1827,100 @@ find_param_path_info(RelOptInfo *rel, Relids required_outer)
return NULL;
}
/*
* get_param_path_clause_serials
* Given a parameterized Path, return the set of pushed-down clauses
* (identified by rinfo_serial numbers) enforced within the Path.
*/
Bitmapset *
get_param_path_clause_serials(Path *path)
{
if (path->param_info == NULL)
return NULL; /* not parameterized */
if (IsA(path, NestPath) ||
IsA(path, MergePath) ||
IsA(path, HashPath))
{
/*
* For a join path, combine clauses enforced within either input path
* with those enforced as joinrestrictinfo in this path. Note that
* joinrestrictinfo may include some non-pushed-down clauses, but for
* current purposes it's okay if we include those in the result. (To
* be more careful, we could check for clause_relids overlapping the
* path parameterization, but it's not worth the cycles for now.)
*/
JoinPath *jpath = (JoinPath *) path;
Bitmapset *pserials;
ListCell *lc;
pserials = NULL;
pserials = bms_add_members(pserials,
get_param_path_clause_serials(jpath->outerjoinpath));
pserials = bms_add_members(pserials,
get_param_path_clause_serials(jpath->innerjoinpath));
foreach(lc, jpath->joinrestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
pserials = bms_add_member(pserials, rinfo->rinfo_serial);
}
return pserials;
}
else if (IsA(path, AppendPath))
{
/*
* For an appendrel, take the intersection of the sets of clauses
* enforced in each input path.
*/
AppendPath *apath = (AppendPath *) path;
Bitmapset *pserials;
ListCell *lc;
pserials = NULL;
foreach(lc, apath->subpaths)
{
Path *subpath = (Path *) lfirst(lc);
Bitmapset *subserials;
subserials = get_param_path_clause_serials(subpath);
if (lc == list_head(apath->subpaths))
pserials = bms_copy(subserials);
else
pserials = bms_int_members(pserials, subserials);
}
return pserials;
}
else if (IsA(path, MergeAppendPath))
{
/* Same as AppendPath case */
MergeAppendPath *apath = (MergeAppendPath *) path;
Bitmapset *pserials;
ListCell *lc;
pserials = NULL;
foreach(lc, apath->subpaths)
{
Path *subpath = (Path *) lfirst(lc);
Bitmapset *subserials;
subserials = get_param_path_clause_serials(subpath);
if (lc == list_head(apath->subpaths))
pserials = bms_copy(subserials);
else
pserials = bms_int_members(pserials, subserials);
}
return pserials;
}
else
{
/*
* Otherwise, it's a baserel path and we can use the
* previously-computed set of serial numbers.
*/
return path->param_info->ppi_serials;
}
}
/*
* build_joinrel_partition_info
* Checks if the two relations being joined can use partitionwise join
@ -1664,9 +1928,10 @@ find_param_path_info(RelOptInfo *rel, Relids required_outer)
* partitioned join relation.
*/
static void
build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, List *restrictlist,
JoinType jointype)
build_joinrel_partition_info(PlannerInfo *root,
RelOptInfo *joinrel, RelOptInfo *outer_rel,
RelOptInfo *inner_rel, SpecialJoinInfo *sjinfo,
List *restrictlist)
{
PartitionScheme part_scheme;
@ -1692,8 +1957,8 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
!outer_rel->consider_partitionwise_join ||
!inner_rel->consider_partitionwise_join ||
outer_rel->part_scheme != inner_rel->part_scheme ||
!have_partkey_equi_join(joinrel, outer_rel, inner_rel,
jointype, restrictlist))
!have_partkey_equi_join(root, joinrel, outer_rel, inner_rel,
sjinfo->jointype, restrictlist))
{
Assert(!IS_PARTITIONED_REL(joinrel));
return;
@ -1717,7 +1982,8 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
* child-join relations of the join relation in try_partitionwise_join().
*/
joinrel->part_scheme = part_scheme;
set_joinrel_partition_key_exprs(joinrel, outer_rel, inner_rel, jointype);
set_joinrel_partition_key_exprs(joinrel, outer_rel, inner_rel,
sjinfo->jointype);
/*
* Set the consider_partitionwise_join flag.
@ -1735,7 +2001,7 @@ build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
* partition keys.
*/
static bool
have_partkey_equi_join(RelOptInfo *joinrel,
have_partkey_equi_join(PlannerInfo *root, RelOptInfo *joinrel,
RelOptInfo *rel1, RelOptInfo *rel2,
JoinType jointype, List *restrictlist)
{
@ -1800,6 +2066,24 @@ have_partkey_equi_join(RelOptInfo *joinrel,
*/
strict_op = op_strict(opexpr->opno);
/*
* Vars appearing in the relation's partition keys will not have any
* varnullingrels, but those in expr1 and expr2 will if we're above
* outer joins that could null the respective rels. It's okay to
* match anyway, if the join operator is strict.
*/
if (strict_op)
{
if (bms_overlap(rel1->relids, root->outer_join_rels))
expr1 = (Expr *) remove_nulling_relids((Node *) expr1,
root->outer_join_rels,
NULL);
if (bms_overlap(rel2->relids, root->outer_join_rels))
expr2 = (Expr *) remove_nulling_relids((Node *) expr2,
root->outer_join_rels,
NULL);
}
/*
* Only clauses referencing the partition keys are useful for
* partitionwise join.
@ -2012,7 +2296,12 @@ set_joinrel_partition_key_exprs(RelOptInfo *joinrel,
* partitionwise nesting of any outer join.) We assume no
* type coercions are needed to make the coalesce expressions,
* since columns of different types won't have gotten
* classified as the same PartitionScheme.
* classified as the same PartitionScheme. Note that we
* intentionally leave out the varnullingrels decoration that
* would ordinarily appear on the Vars inside these
* CoalesceExprs, because have_partkey_equi_join will strip
* varnullingrels from the expressions it will compare to the
* partexprs.
*/
foreach(lc, list_concat_copy(outer_expr, outer_null_expr))
{

View File

@ -53,6 +53,10 @@ static Expr *make_sub_restrictinfos(PlannerInfo *root,
* required_relids can be NULL, in which case it defaults to the actual clause
* contents (i.e., clause_relids).
*
* Note that there aren't options to set the has_clone and is_clone flags:
* we always initialize those to false. There's just one place that wants
* something different, so making all callers pass them seems inconvenient.
*
* We initialize fields that depend only on the given subexpression, leaving
* others that depend on context (or may never be needed at all) to be filled
* later.
@ -116,12 +120,15 @@ make_restrictinfo_internal(PlannerInfo *root,
Relids nullable_relids)
{
RestrictInfo *restrictinfo = makeNode(RestrictInfo);
Relids baserels;
restrictinfo->clause = clause;
restrictinfo->orclause = orclause;
restrictinfo->is_pushed_down = is_pushed_down;
restrictinfo->outerjoin_delayed = outerjoin_delayed;
restrictinfo->pseudoconstant = pseudoconstant;
restrictinfo->has_clone = false; /* may get set by caller */
restrictinfo->is_clone = false; /* may get set by caller */
restrictinfo->can_join = false; /* may get set below */
restrictinfo->security_level = security_level;
restrictinfo->outer_relids = outer_relids;
@ -187,6 +194,25 @@ make_restrictinfo_internal(PlannerInfo *root,
else
restrictinfo->required_relids = restrictinfo->clause_relids;
/*
* Count the number of base rels appearing in clause_relids. To do this,
* we just delete rels mentioned in root->outer_join_rels and count the
* survivors. Because we are called during deconstruct_jointree which is
* the same tree walk that populates outer_join_rels, this is a little bit
* unsafe-looking; but it should be fine because the recursion in
* deconstruct_jointree should already have visited any outer join that
* could be mentioned in this clause.
*/
baserels = bms_difference(restrictinfo->clause_relids,
root->outer_join_rels);
restrictinfo->num_base_rels = bms_num_members(baserels);
bms_free(baserels);
/*
* Label this RestrictInfo with a fresh serial number.
*/
restrictinfo->rinfo_serial = ++(root->last_rinfo_serial);
/*
* Fill in all the cacheable fields with "not yet set" markers. None of
* these will be computed until/unless needed. Note in particular that we
@ -350,7 +376,7 @@ commute_restrictinfo(RestrictInfo *rinfo, Oid comm_op)
* ... and adjust those we need to change. Note in particular that we can
* preserve any cached selectivity or cost estimates, since those ought to
* be the same for the new clause. Likewise we can keep the source's
* parent_ec.
* parent_ec. It's also important that we keep the same rinfo_serial.
*/
result->clause = (Expr *) newclause;
result->left_relids = rinfo->right_relids;
@ -497,6 +523,58 @@ extract_actual_join_clauses(List *restrictinfo_list,
}
}
/*
* clause_is_computable_at
* Test whether a clause is computable at a given evaluation level.
*
* There are two conditions for whether an expression can actually be
* evaluated at a given join level: the evaluation context must include
* all the relids (both base and OJ) used by the expression, and we must
* not have already evaluated any outer joins that null Vars/PHVs of the
* expression and are not listed in their nullingrels.
*
* This function checks the second condition; we assume the caller already
* saw to the first one.
*
* For speed reasons, we don't individually examine each Var/PHV of the
* expression, but just look at the overall clause_relids (the union of the
* varnos and varnullingrels). This could give a misleading answer if the
* Vars of a given varno don't all have the same varnullingrels; but that
* really shouldn't happen within a single scalar expression or RestrictInfo
* clause. Despite that, this is still annoyingly expensive :-(
*/
bool
clause_is_computable_at(PlannerInfo *root,
Relids clause_relids,
Relids eval_relids)
{
ListCell *lc;
/* Nothing to do if no outer joins have been performed yet. */
if (!bms_overlap(eval_relids, root->outer_join_rels))
return true;
foreach(lc, root->join_info_list)
{
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(lc);
/* Ignore outer joins that are not yet performed. */
if (!bms_is_member(sjinfo->ojrelid, eval_relids))
continue;
/* OK if clause lists it (we assume all Vars in it agree). */
if (bms_is_member(sjinfo->ojrelid, clause_relids))
continue;
/* Else, trouble if clause mentions any nullable Vars. */
if (bms_overlap(clause_relids, sjinfo->min_righthand) ||
(sjinfo->jointype == JOIN_FULL &&
bms_overlap(clause_relids, sjinfo->min_lefthand)))
return false; /* doesn't work */
}
return true; /* OK */
}
/*
* join_clause_is_movable_to
@ -522,6 +600,12 @@ extract_actual_join_clauses(List *restrictinfo_list,
* Also, the join clause must not use any relations that have LATERAL
* references to the target relation, since we could not put such rels on
* the outer side of a nestloop with the target relation.
*
* Also, we reject is_clone versions of outer-join clauses. This has the
* effect of preventing us from generating variant parameterized paths
* that differ only in which outer joins null the parameterization rel(s).
* Generating one path from the minimally-parameterized has_clone version
* is sufficient.
*/
bool
join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
@ -542,6 +626,10 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
if (bms_overlap(baserel->lateral_referencers, rinfo->clause_relids))
return false;
/* Ignore clones, too */
if (rinfo->is_clone)
return false;
return true;
}
@ -587,6 +675,9 @@ join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel)
* moved for some valid set of outer rels, so we don't have the benefit of
* relying on prior checks for lateral-reference validity.
*
* Likewise, we don't check is_clone here: rejecting the inappropriate
* variants of a cloned clause must be handled upstream.
*
* Note: if this returns true, it means that the clause could be moved to
* this join relation, but that doesn't mean that this is the lowest join
* it could be moved to. Caller may need to make additional calls to verify

View File

@ -62,6 +62,7 @@ typedef struct
typedef struct
{
PlannerInfo *root; /* could be NULL! */
Query *query; /* outer Query */
int sublevels_up;
bool possible_sublink; /* could aliases include a SubLink? */
@ -80,6 +81,10 @@ static bool pull_var_clause_walker(Node *node,
pull_var_clause_context *context);
static Node *flatten_join_alias_vars_mutator(Node *node,
flatten_join_alias_vars_context *context);
static Node *add_nullingrels_if_needed(PlannerInfo *root, Node *newnode,
Var *oldvar);
static bool is_standard_join_alias_expression(Node *newnode, Var *oldvar);
static void adjust_standard_join_alias_expression(Node *newnode, Var *oldvar);
static Relids alias_relid_set(Query *query, Relids relids);
@ -88,6 +93,9 @@ static Relids alias_relid_set(Query *query, Relids relids);
* Create a set of all the distinct varnos present in a parsetree.
* Only varnos that reference level-zero rtable entries are considered.
*
* The result includes outer-join relids mentioned in Var.varnullingrels and
* PlaceHolderVar.phnullingrels fields in the parsetree.
*
* "root" can be passed as NULL if it is not necessary to process
* PlaceHolderVars.
*
@ -153,7 +161,11 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up)
{
context->varnos = bms_add_member(context->varnos, var->varno);
context->varnos = bms_add_members(context->varnos,
var->varnullingrels);
}
return false;
}
if (IsA(node, CurrentOfExpr))
@ -244,6 +256,14 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
context->varnos = bms_join(context->varnos,
newevalat);
}
/*
* In all three cases, include phnullingrels in the result. We
* don't worry about possibly needing to translate it, because
* appendrels only translate varnos of baserels, not outer joins.
*/
context->varnos = bms_add_members(context->varnos,
phv->phnullingrels);
return false; /* don't recurse into expression */
}
}
@ -707,26 +727,42 @@ pull_var_clause_walker(Node *node, pull_var_clause_context *context)
* is the only way that the executor can directly handle whole-row Vars.
*
* This also adjusts relid sets found in some expression node types to
* substitute the contained base rels for any join relid.
* substitute the contained base+OJ rels for any join relid.
*
* If a JOIN contains sub-selects that have been flattened, its join alias
* entries might now be arbitrary expressions, not just Vars. This affects
* this function in one important way: we might find ourselves inserting
* SubLink expressions into subqueries, and we must make sure that their
* Query.hasSubLinks fields get set to true if so. If there are any
* this function in two important ways. First, we might find ourselves
* inserting SubLink expressions into subqueries, and we must make sure that
* their Query.hasSubLinks fields get set to true if so. If there are any
* SubLinks in the join alias lists, the outer Query should already have
* hasSubLinks = true, so this is only relevant to un-flattened subqueries.
* Second, we have to preserve any varnullingrels info attached to the
* alias Vars we're replacing. If the replacement expression is a Var or
* PlaceHolderVar or constructed from those, we can just add the
* varnullingrels bits to the existing nullingrels field(s); otherwise
* we have to add a PlaceHolderVar wrapper.
*
* NOTE: this is used on not-yet-planned expressions. We do not expect it
* to be applied directly to the whole Query, so if we see a Query to start
* with, we do want to increment sublevels_up (this occurs for LATERAL
* subqueries).
* NOTE: this is also used by the parser, to expand join alias Vars before
* checking GROUP BY validity. For that use-case, root will be NULL, which
* is why we have to pass the Query separately. We need the root itself only
* for making PlaceHolderVars. We can avoid making PlaceHolderVars in the
* parser's usage because it won't be dealing with arbitrary expressions:
* so long as adjust_standard_join_alias_expression can handle everything
* the parser would make as a join alias expression, we're OK.
*/
Node *
flatten_join_alias_vars(Query *query, Node *node)
flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node)
{
flatten_join_alias_vars_context context;
/*
* We do not expect this to be applied to the whole Query, only to
* expressions or LATERAL subqueries. Hence, if the top node is a Query,
* it's okay to immediately increment sublevels_up.
*/
Assert(node != (Node *) query);
context.root = root;
context.query = query;
context.sublevels_up = 0;
/* flag whether join aliases could possibly contain SubLinks */
@ -797,7 +833,9 @@ flatten_join_alias_vars_mutator(Node *node,
rowexpr->colnames = colnames;
rowexpr->location = var->location;
return (Node *) rowexpr;
/* Lastly, add any varnullingrels to the replacement expression */
return add_nullingrels_if_needed(context->root, (Node *) rowexpr,
var);
}
/* Expand join alias reference */
@ -824,7 +862,8 @@ flatten_join_alias_vars_mutator(Node *node,
if (context->possible_sublink && !context->inserted_sublink)
context->inserted_sublink = checkExprHasSubLink(newvar);
return newvar;
/* Lastly, add any varnullingrels to the replacement expression */
return add_nullingrels_if_needed(context->root, newvar, var);
}
if (IsA(node, PlaceHolderVar))
{
@ -839,6 +878,7 @@ flatten_join_alias_vars_mutator(Node *node,
{
phv->phrels = alias_relid_set(context->query,
phv->phrels);
/* we *don't* change phnullingrels */
}
return (Node *) phv;
}
@ -872,9 +912,197 @@ flatten_join_alias_vars_mutator(Node *node,
(void *) context);
}
/*
* Add oldvar's varnullingrels, if any, to a flattened join alias expression.
* The newnode has been copied, so we can modify it freely.
*/
static Node *
add_nullingrels_if_needed(PlannerInfo *root, Node *newnode, Var *oldvar)
{
if (oldvar->varnullingrels == NULL)
return newnode; /* nothing to do */
/* If possible, do it by adding to existing nullingrel fields */
if (is_standard_join_alias_expression(newnode, oldvar))
adjust_standard_join_alias_expression(newnode, oldvar);
else if (root)
{
/*
* We can insert a PlaceHolderVar to carry the nullingrels. However,
* deciding where to evaluate the PHV is slightly tricky. We first
* try to evaluate it at the natural semantic level of the new
* expression; but if that expression is variable-free, fall back to
* evaluating it at the join that the oldvar is an alias Var for.
*/
PlaceHolderVar *newphv;
Index levelsup = oldvar->varlevelsup;
Relids phrels = pull_varnos_of_level(root, newnode, levelsup);
if (bms_is_empty(phrels)) /* variable-free? */
{
if (levelsup != 0) /* this won't work otherwise */
elog(ERROR, "unsupported join alias expression");
phrels = get_relids_for_join(root->parse, oldvar->varno);
/* If it's an outer join, eval below not above the join */
phrels = bms_del_member(phrels, oldvar->varno);
Assert(!bms_is_empty(phrels));
}
newphv = make_placeholder_expr(root, (Expr *) newnode, phrels);
/* newphv has zero phlevelsup and NULL phnullingrels; fix it */
newphv->phlevelsup = levelsup;
newphv->phnullingrels = bms_copy(oldvar->varnullingrels);
newnode = (Node *) newphv;
}
else
{
/* ooops, we're missing support for something the parser can make */
elog(ERROR, "unsupported join alias expression");
}
return newnode;
}
/*
* Check to see if we can insert nullingrels into this join alias expression
* without use of a separate PlaceHolderVar.
*
* This will handle Vars, PlaceHolderVars, and implicit-coercion and COALESCE
* expressions built from those. This coverage needs to handle anything
* that the parser would put into joinaliasvars.
*/
static bool
is_standard_join_alias_expression(Node *newnode, Var *oldvar)
{
if (newnode == NULL)
return false;
if (IsA(newnode, Var) &&
((Var *) newnode)->varlevelsup == oldvar->varlevelsup)
return true;
else if (IsA(newnode, PlaceHolderVar) &&
((PlaceHolderVar *) newnode)->phlevelsup == oldvar->varlevelsup)
return true;
else if (IsA(newnode, FuncExpr))
{
FuncExpr *fexpr = (FuncExpr *) newnode;
/*
* We need to assume that the function wouldn't produce non-NULL from
* NULL, which is reasonable for implicit coercions but otherwise not
* so much. (Looking at its strictness is likely overkill, and anyway
* it would cause us to fail if someone forgot to mark an implicit
* coercion as strict.)
*/
if (fexpr->funcformat != COERCE_IMPLICIT_CAST ||
fexpr->args == NIL)
return false;
/*
* Examine only the first argument --- coercions might have additional
* arguments that are constants.
*/
return is_standard_join_alias_expression(linitial(fexpr->args), oldvar);
}
else if (IsA(newnode, RelabelType))
{
RelabelType *relabel = (RelabelType *) newnode;
/* This definitely won't produce non-NULL from NULL */
return is_standard_join_alias_expression((Node *) relabel->arg, oldvar);
}
else if (IsA(newnode, CoerceViaIO))
{
CoerceViaIO *iocoerce = (CoerceViaIO *) newnode;
/* This definitely won't produce non-NULL from NULL */
return is_standard_join_alias_expression((Node *) iocoerce->arg, oldvar);
}
else if (IsA(newnode, ArrayCoerceExpr))
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) newnode;
/* This definitely won't produce non-NULL from NULL (at array level) */
return is_standard_join_alias_expression((Node *) acoerce->arg, oldvar);
}
else if (IsA(newnode, CoalesceExpr))
{
CoalesceExpr *cexpr = (CoalesceExpr *) newnode;
ListCell *lc;
Assert(cexpr->args != NIL);
foreach(lc, cexpr->args)
{
if (!is_standard_join_alias_expression(lfirst(lc), oldvar))
return false;
}
return true;
}
else
return false;
}
/*
* Insert nullingrels into an expression accepted by
* is_standard_join_alias_expression.
*/
static void
adjust_standard_join_alias_expression(Node *newnode, Var *oldvar)
{
if (IsA(newnode, Var) &&
((Var *) newnode)->varlevelsup == oldvar->varlevelsup)
{
Var *newvar = (Var *) newnode;
newvar->varnullingrels = bms_add_members(newvar->varnullingrels,
oldvar->varnullingrels);
}
else if (IsA(newnode, PlaceHolderVar) &&
((PlaceHolderVar *) newnode)->phlevelsup == oldvar->varlevelsup)
{
PlaceHolderVar *newphv = (PlaceHolderVar *) newnode;
newphv->phnullingrels = bms_add_members(newphv->phnullingrels,
oldvar->varnullingrels);
}
else if (IsA(newnode, FuncExpr))
{
FuncExpr *fexpr = (FuncExpr *) newnode;
adjust_standard_join_alias_expression(linitial(fexpr->args), oldvar);
}
else if (IsA(newnode, RelabelType))
{
RelabelType *relabel = (RelabelType *) newnode;
adjust_standard_join_alias_expression((Node *) relabel->arg, oldvar);
}
else if (IsA(newnode, CoerceViaIO))
{
CoerceViaIO *iocoerce = (CoerceViaIO *) newnode;
adjust_standard_join_alias_expression((Node *) iocoerce->arg, oldvar);
}
else if (IsA(newnode, ArrayCoerceExpr))
{
ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) newnode;
adjust_standard_join_alias_expression((Node *) acoerce->arg, oldvar);
}
else if (IsA(newnode, CoalesceExpr))
{
CoalesceExpr *cexpr = (CoalesceExpr *) newnode;
ListCell *lc;
Assert(cexpr->args != NIL);
foreach(lc, cexpr->args)
{
adjust_standard_join_alias_expression(lfirst(lc), oldvar);
}
}
else
Assert(false);
}
/*
* alias_relid_set: in a set of RT indexes, replace joins by their
* underlying base relids
* underlying base+OJ relids
*/
static Relids
alias_relid_set(Query *query, Relids relids)

View File

@ -676,6 +676,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
sub_pstate->p_rtable = sub_rtable;
sub_pstate->p_rteperminfos = sub_rteperminfos;
sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
sub_pstate->p_nullingrels = NIL;
sub_pstate->p_namespace = sub_namespace;
sub_pstate->p_resolve_unknowns = false;
@ -857,7 +858,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
/*
* Generate list of Vars referencing the RTE
*/
exprList = expandNSItemVars(nsitem, 0, -1, NULL);
exprList = expandNSItemVars(pstate, nsitem, 0, -1, NULL);
/*
* Re-apply any indirection on the target column specs to the Vars

View File

@ -1167,7 +1167,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
* entries are RTE_JOIN kind.
*/
if (hasJoinRTEs)
groupClauses = (List *) flatten_join_alias_vars(qry,
groupClauses = (List *) flatten_join_alias_vars(NULL, qry,
(Node *) groupClauses);
/*
@ -1211,7 +1211,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
groupClauses, hasJoinRTEs,
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(qry, clause);
clause = flatten_join_alias_vars(NULL, qry, clause);
check_ungrouped_columns(clause, pstate, qry,
groupClauses, groupClauseCommonVars,
have_non_var_grouping,
@ -1222,7 +1222,7 @@ parseCheckAggregates(ParseState *pstate, Query *qry)
groupClauses, hasJoinRTEs,
have_non_var_grouping);
if (hasJoinRTEs)
clause = flatten_join_alias_vars(qry, clause);
clause = flatten_join_alias_vars(NULL, qry, clause);
check_ungrouped_columns(clause, pstate, qry,
groupClauses, groupClauseCommonVars,
have_non_var_grouping,
@ -1551,7 +1551,7 @@ finalize_grouping_exprs_walker(Node *node,
Index ref = 0;
if (context->hasJoinRTEs)
expr = flatten_join_alias_vars(context->qry, expr);
expr = flatten_join_alias_vars(NULL, context->qry, expr);
/*
* Each expression must match a grouping entry at the current

View File

@ -52,7 +52,8 @@
#include "utils/syscache.h"
static int extractRemainingColumns(ParseNamespaceColumn *src_nscolumns,
static int extractRemainingColumns(ParseState *pstate,
ParseNamespaceColumn *src_nscolumns,
List *src_colnames,
List **src_colnos,
List **res_colnames, List **res_colvars,
@ -75,9 +76,11 @@ static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate,
static Node *transformFromClauseItem(ParseState *pstate, Node *n,
ParseNamespaceItem **top_nsitem,
List **namespace);
static Var *buildVarFromNSColumn(ParseNamespaceColumn *nscol);
static Var *buildVarFromNSColumn(ParseState *pstate,
ParseNamespaceColumn *nscol);
static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
Var *l_colvar, Var *r_colvar);
static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex);
static void setNamespaceColumnVisibility(List *namespace, bool cols_visible);
static void setNamespaceLateralState(List *namespace,
bool lateral_only, bool lateral_ok);
@ -251,7 +254,8 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
* Returns the number of columns added.
*/
static int
extractRemainingColumns(ParseNamespaceColumn *src_nscolumns,
extractRemainingColumns(ParseState *pstate,
ParseNamespaceColumn *src_nscolumns,
List *src_colnames,
List **src_colnos,
List **res_colnames, List **res_colvars,
@ -287,7 +291,8 @@ extractRemainingColumns(ParseNamespaceColumn *src_nscolumns,
*src_colnos = lappend_int(*src_colnos, attnum);
*res_colnames = lappend(*res_colnames, lfirst(lc));
*res_colvars = lappend(*res_colvars,
buildVarFromNSColumn(src_nscolumns + attnum - 1));
buildVarFromNSColumn(pstate,
src_nscolumns + attnum - 1));
/* Copy the input relation's nscolumn data for this column */
res_nscolumns[colcount] = src_nscolumns[attnum - 1];
colcount++;
@ -1288,8 +1293,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
{
/*
* JOIN/USING (or NATURAL JOIN, as transformed above). Transform
* the list into an explicit ON-condition, and generate a list of
* merged result columns.
* the list into an explicit ON-condition.
*/
List *ucols = j->usingClause;
List *l_usingvars = NIL;
@ -1307,8 +1311,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
int r_index = -1;
Var *l_colvar,
*r_colvar;
Node *u_colvar;
ParseNamespaceColumn *res_nscolumn;
Assert(u_colname[0] != '\0');
@ -1372,17 +1374,109 @@ transformFromClauseItem(ParseState *pstate, Node *n,
u_colname)));
r_colnos = lappend_int(r_colnos, r_index + 1);
l_colvar = buildVarFromNSColumn(l_nscolumns + l_index);
/* Build Vars to use in the generated JOIN ON clause */
l_colvar = buildVarFromNSColumn(pstate, l_nscolumns + l_index);
l_usingvars = lappend(l_usingvars, l_colvar);
r_colvar = buildVarFromNSColumn(r_nscolumns + r_index);
r_colvar = buildVarFromNSColumn(pstate, r_nscolumns + r_index);
r_usingvars = lappend(r_usingvars, r_colvar);
/*
* While we're here, add column names to the res_colnames
* list. It's a bit ugly to do this here while the
* corresponding res_colvars entries are not made till later,
* but doing this later would require an additional traversal
* of the usingClause list.
*/
res_colnames = lappend(res_colnames, lfirst(ucol));
}
/* Construct the generated JOIN ON clause */
j->quals = transformJoinUsingClause(pstate,
l_usingvars,
r_usingvars);
}
else if (j->quals)
{
/* User-written ON-condition; transform it */
j->quals = transformJoinOnClause(pstate, j, my_namespace);
}
else
{
/* CROSS JOIN: no quals */
}
/*
* If this is an outer join, now mark the appropriate child RTEs as
* being nulled by this join. We have finished processing the child
* join expressions as well as the current join's quals, which deal in
* non-nulled input columns. All future references to those RTEs will
* see possibly-nulled values, and we should mark generated Vars to
* account for that. In particular, the join alias Vars that we're
* about to build should reflect the nulling effects of this join.
*
* A difficulty with doing this is that we need the join's RT index,
* which we don't officially have yet. However, no other RTE can get
* made between here and the addRangeTableEntryForJoin call, so we can
* predict what the assignment will be. (Alternatively, we could call
* addRangeTableEntryForJoin before we have all the data computed, but
* this seems less ugly.)
*/
j->rtindex = list_length(pstate->p_rtable) + 1;
switch (j->jointype)
{
case JOIN_INNER:
break;
case JOIN_LEFT:
markRelsAsNulledBy(pstate, j->rarg, j->rtindex);
break;
case JOIN_FULL:
markRelsAsNulledBy(pstate, j->larg, j->rtindex);
markRelsAsNulledBy(pstate, j->rarg, j->rtindex);
break;
case JOIN_RIGHT:
markRelsAsNulledBy(pstate, j->larg, j->rtindex);
break;
default:
/* shouldn't see any other types here */
elog(ERROR, "unrecognized join type: %d",
(int) j->jointype);
break;
}
/*
* Now we can construct join alias expressions for the USING columns.
*/
if (j->usingClause)
{
ListCell *lc1,
*lc2;
/* Scan the colnos lists to recover info from the previous loop */
forboth(lc1, l_colnos, lc2, r_colnos)
{
int l_index = lfirst_int(lc1) - 1;
int r_index = lfirst_int(lc2) - 1;
Var *l_colvar,
*r_colvar;
Node *u_colvar;
ParseNamespaceColumn *res_nscolumn;
/*
* Note we re-build these Vars: they might have different
* varnullingrels than the ones made in the previous loop.
*/
l_colvar = buildVarFromNSColumn(pstate, l_nscolumns + l_index);
r_colvar = buildVarFromNSColumn(pstate, r_nscolumns + r_index);
/* Construct the join alias Var for this column */
u_colvar = buildMergedJoinVar(pstate,
j->jointype,
l_colvar,
r_colvar);
res_colvars = lappend(res_colvars, u_colvar);
/* Construct column's res_nscolumns[] entry */
res_nscolumn = res_nscolumns + res_colindex;
res_colindex++;
if (u_colvar == (Node *) l_colvar)
@ -1400,47 +1494,45 @@ transformFromClauseItem(ParseState *pstate, Node *n,
/*
* Merged column is not semantically equivalent to either
* input, so it needs to be referenced as the join output
* column. We don't know the join's varno yet, so we'll
* replace these zeroes below.
* column.
*/
res_nscolumn->p_varno = 0;
res_nscolumn->p_varno = j->rtindex;
res_nscolumn->p_varattno = res_colindex;
res_nscolumn->p_vartype = exprType(u_colvar);
res_nscolumn->p_vartypmod = exprTypmod(u_colvar);
res_nscolumn->p_varcollid = exprCollation(u_colvar);
res_nscolumn->p_varnosyn = 0;
res_nscolumn->p_varnosyn = j->rtindex;
res_nscolumn->p_varattnosyn = res_colindex;
}
}
j->quals = transformJoinUsingClause(pstate,
l_usingvars,
r_usingvars);
}
else if (j->quals)
{
/* User-written ON-condition; transform it */
j->quals = transformJoinOnClause(pstate, j, my_namespace);
}
else
{
/* CROSS JOIN: no quals */
}
/* Add remaining columns from each side to the output columns */
res_colindex +=
extractRemainingColumns(l_nscolumns, l_colnames, &l_colnos,
extractRemainingColumns(pstate,
l_nscolumns, l_colnames, &l_colnos,
&res_colnames, &res_colvars,
res_nscolumns + res_colindex);
res_colindex +=
extractRemainingColumns(r_nscolumns, r_colnames, &r_colnos,
extractRemainingColumns(pstate,
r_nscolumns, r_colnames, &r_colnos,
&res_colnames, &res_colvars,
res_nscolumns + res_colindex);
/* If join has an alias, it syntactically hides all inputs */
if (j->alias)
{
for (k = 0; k < res_colindex; k++)
{
ParseNamespaceColumn *nscol = res_nscolumns + k;
nscol->p_varnosyn = j->rtindex;
nscol->p_varattnosyn = k + 1;
}
}
/*
* Now build an RTE and nsitem for the result of the join.
* res_nscolumns isn't totally done yet, but that's OK because
* addRangeTableEntryForJoin doesn't examine it, only store a pointer.
*/
nsitem = addRangeTableEntryForJoin(pstate,
res_colnames,
@ -1454,31 +1546,16 @@ transformFromClauseItem(ParseState *pstate, Node *n,
j->alias,
true);
j->rtindex = nsitem->p_rtindex;
/* Verify that we correctly predicted the join's RT index */
Assert(j->rtindex == nsitem->p_rtindex);
/* Cross-check number of columns, too */
Assert(res_colindex == list_length(nsitem->p_names->colnames));
/*
* Now that we know the join RTE's rangetable index, we can fix up the
* res_nscolumns data in places where it should contain that.
* Save a link to the JoinExpr in the proper element of p_joinexprs.
* Since we maintain that list lazily, it may be necessary to fill in
* empty entries before we can add the JoinExpr in the right place.
*/
Assert(res_colindex == list_length(nsitem->p_names->colnames));
for (k = 0; k < res_colindex; k++)
{
ParseNamespaceColumn *nscol = res_nscolumns + k;
/* fill in join RTI for merged columns */
if (nscol->p_varno == 0)
nscol->p_varno = j->rtindex;
if (nscol->p_varnosyn == 0)
nscol->p_varnosyn = j->rtindex;
/* if join has an alias, it syntactically hides all inputs */
if (j->alias)
{
nscol->p_varnosyn = j->rtindex;
nscol->p_varattnosyn = k + 1;
}
}
/* make a matching link to the JoinExpr for later use */
for (k = list_length(pstate->p_joinexprs) + 1; k < j->rtindex; k++)
pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL);
pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
@ -1547,10 +1624,13 @@ transformFromClauseItem(ParseState *pstate, Node *n,
* buildVarFromNSColumn -
* build a Var node using ParseNamespaceColumn data
*
* We assume varlevelsup should be 0, and no location is specified
* This is used to construct joinaliasvars entries.
* We can assume varlevelsup should be 0, and no location is specified.
* Note also that no column SELECT privilege is requested here; that would
* happen only if the column is actually referenced in the query.
*/
static Var *
buildVarFromNSColumn(ParseNamespaceColumn *nscol)
buildVarFromNSColumn(ParseState *pstate, ParseNamespaceColumn *nscol)
{
Var *var;
@ -1564,6 +1644,10 @@ buildVarFromNSColumn(ParseNamespaceColumn *nscol)
/* makeVar doesn't offer parameters for these, so set by hand: */
var->varnosyn = nscol->p_varnosyn;
var->varattnosyn = nscol->p_varattnosyn;
/* ... and update varnullingrels */
markNullableIfNeeded(pstate, var);
return var;
}
@ -1675,6 +1759,47 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
return res_node;
}
/*
* markRelsAsNulledBy -
* Mark the given jointree node and its children as nulled by join jindex
*/
static void
markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex)
{
int varno;
ListCell *lc;
/* Note: we can't see FromExpr here */
if (IsA(n, RangeTblRef))
{
varno = ((RangeTblRef *) n)->rtindex;
}
else if (IsA(n, JoinExpr))
{
JoinExpr *j = (JoinExpr *) n;
/* recurse to children */
markRelsAsNulledBy(pstate, j->larg, jindex);
markRelsAsNulledBy(pstate, j->rarg, jindex);
varno = j->rtindex;
}
else
{
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n));
varno = 0; /* keep compiler quiet */
}
/*
* Now add jindex to the p_nullingrels set for relation varno. Since we
* maintain the p_nullingrels list lazily, we might need to extend it to
* make the varno'th entry exist.
*/
while (list_length(pstate->p_nullingrels) < varno)
pstate->p_nullingrels = lappend(pstate->p_nullingrels, NULL);
lc = list_nth_cell(pstate->p_nullingrels, varno - 1);
lfirst(lc) = bms_add_member((Bitmapset *) lfirst(lc), jindex);
}
/*
* setNamespaceColumnVisibility -
* Convenience subroutine to update cols_visible flags in a namespace list.

View File

@ -1042,7 +1042,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
ParseNamespaceItem *nsitem;
nsitem = GetNSItemByRangeTablePosn(pstate, rtindex, sublevels_up);
args = expandNSItemVars(nsitem, sublevels_up, vlocation, NULL);
args = expandNSItemVars(pstate, nsitem, sublevels_up, vlocation, NULL);
}
else
ereport(ERROR,

View File

@ -2478,6 +2478,9 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
/* location is not filled in by makeWholeRowVar */
result->location = location;
/* mark Var if it's nulled by any outer joins */
markNullableIfNeeded(pstate, result);
/* mark relation as requiring whole-row SELECT access */
markVarForSelectPriv(pstate, result);
@ -2505,6 +2508,8 @@ transformWholeRowRef(ParseState *pstate, ParseNamespaceItem *nsitem,
rowexpr->colnames = copyObject(nsitem->p_names->colnames);
rowexpr->location = location;
/* XXX we ought to mark the row as possibly nullable */
return (Node *) rowexpr;
}
}

View File

@ -763,6 +763,9 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
}
var->location = location;
/* Mark Var if it's nulled by any outer joins */
markNullableIfNeeded(pstate, var);
/* Require read access to the column */
markVarForSelectPriv(pstate, var);
@ -1023,6 +1026,35 @@ searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colnam
return fuzzystate;
}
/*
* markNullableIfNeeded
* If the RTE referenced by the Var is nullable by outer join(s)
* at this point in the query, set var->varnullingrels to show that.
*/
void
markNullableIfNeeded(ParseState *pstate, Var *var)
{
int rtindex = var->varno;
Bitmapset *relids;
/* Find the appropriate pstate */
for (int lv = 0; lv < var->varlevelsup; lv++)
pstate = pstate->parentParseState;
/* Find currently-relevant join relids for the Var's rel */
if (rtindex > 0 && rtindex <= list_length(pstate->p_nullingrels))
relids = (Bitmapset *) list_nth(pstate->p_nullingrels, rtindex - 1);
else
relids = NULL;
/*
* Merge with any already-declared nulling rels. (Typically there won't
* be any, but let's get it right if there are.)
*/
if (relids != NULL)
var->varnullingrels = bms_union(var->varnullingrels, relids);
}
/*
* markRTEForSelectPriv
* Mark the specified column of the RTE with index rtindex
@ -3087,7 +3119,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
* the list elements mustn't be modified.
*/
List *
expandNSItemVars(ParseNamespaceItem *nsitem,
expandNSItemVars(ParseState *pstate, ParseNamespaceItem *nsitem,
int sublevels_up, int location,
List **colnames)
{
@ -3123,6 +3155,10 @@ expandNSItemVars(ParseNamespaceItem *nsitem,
var->varnosyn = nscol->p_varnosyn;
var->varattnosyn = nscol->p_varattnosyn;
var->location = location;
/* ... and update varnullingrels */
markNullableIfNeeded(pstate, var);
result = lappend(result, var);
if (colnames)
*colnames = lappend(*colnames, colnameval);
@ -3158,7 +3194,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
*var;
List *te_list = NIL;
vars = expandNSItemVars(nsitem, sublevels_up, location, &names);
vars = expandNSItemVars(pstate, nsitem, sublevels_up, location, &names);
/*
* Require read access to the table. This is normally redundant with the

View File

@ -1371,7 +1371,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
List *vars;
ListCell *l;
vars = expandNSItemVars(nsitem, sublevels_up, location, NULL);
vars = expandNSItemVars(pstate, nsitem, sublevels_up, location, NULL);
/*
* Require read access to the table. This is normally redundant with

View File

@ -40,6 +40,20 @@ typedef struct
int win_location;
} locate_windowfunc_context;
typedef struct
{
const Bitmapset *target_relids;
const Bitmapset *added_relids;
int sublevels_up;
} add_nulling_relids_context;
typedef struct
{
const Bitmapset *removable_relids;
const Bitmapset *except_relids;
int sublevels_up;
} remove_nulling_relids_context;
static bool contain_aggs_of_level_walker(Node *node,
contain_aggs_of_level_context *context);
static bool locate_agg_of_level_walker(Node *node,
@ -50,6 +64,10 @@ static bool locate_windowfunc_walker(Node *node,
static bool checkExprHasSubLink_walker(Node *node, void *context);
static Relids offset_relid_set(Relids relids, int offset);
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,
remove_nulling_relids_context *context);
/*
@ -381,6 +399,8 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
if (var->varlevelsup == context->sublevels_up)
{
var->varno += context->offset;
var->varnullingrels = offset_relid_set(var->varnullingrels,
context->offset);
if (var->varnosyn > 0)
var->varnosyn += context->offset;
}
@ -419,6 +439,8 @@ OffsetVarNodes_walker(Node *node, OffsetVarNodes_context *context)
{
phv->phrels = offset_relid_set(phv->phrels,
context->offset);
phv->phnullingrels = offset_relid_set(phv->phnullingrels,
context->offset);
}
/* fall through to examine children */
}
@ -543,11 +565,13 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
{
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up &&
var->varno == context->rt_index)
if (var->varlevelsup == context->sublevels_up)
{
var->varno = context->new_index;
/* If the syntactic referent is same RTE, fix it too */
if (var->varno == context->rt_index)
var->varno = context->new_index;
var->varnullingrels = adjust_relid_set(var->varnullingrels,
context->rt_index,
context->new_index);
if (var->varnosyn == context->rt_index)
var->varnosyn = context->new_index;
}
@ -590,6 +614,9 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context)
phv->phrels = adjust_relid_set(phv->phrels,
context->rt_index,
context->new_index);
phv->phnullingrels = adjust_relid_set(phv->phnullingrels,
context->rt_index,
context->new_index);
}
/* fall through to examine children */
}
@ -866,7 +893,8 @@ rangeTableEntry_used_walker(Node *node,
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up &&
var->varno == context->rt_index)
(var->varno == context->rt_index ||
bms_is_member(context->rt_index, var->varnullingrels)))
return true;
return false;
}
@ -1094,6 +1122,195 @@ AddInvertedQual(Query *parsetree, Node *qual)
}
/*
* add_nulling_relids() finds Vars and PlaceHolderVars that belong to any
* of the target_relids, and adds added_relids to their varnullingrels
* and phnullingrels fields.
*/
Node *
add_nulling_relids(Node *node,
const Bitmapset *target_relids,
const Bitmapset *added_relids)
{
add_nulling_relids_context context;
context.target_relids = target_relids;
context.added_relids = added_relids;
context.sublevels_up = 0;
return query_or_expression_tree_mutator(node,
add_nulling_relids_mutator,
&context,
0);
}
static Node *
add_nulling_relids_mutator(Node *node,
add_nulling_relids_context *context)
{
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up &&
bms_is_member(var->varno, context->target_relids))
{
Relids newnullingrels = bms_union(var->varnullingrels,
context->added_relids);
/* Copy the Var ... */
var = copyObject(var);
/* ... and replace the copy's varnullingrels field */
var->varnullingrels = newnullingrels;
return (Node *) var;
}
/* Otherwise fall through to copy the Var normally */
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
if (phv->phlevelsup == context->sublevels_up &&
bms_overlap(phv->phrels, context->target_relids))
{
Relids newnullingrels = bms_union(phv->phnullingrels,
context->added_relids);
/*
* We don't modify the contents of the PHV's expression, only add
* to phnullingrels. This corresponds to assuming that the PHV
* will be evaluated at the same level as before, then perhaps be
* nulled as it bubbles up. Hence, just flat-copy the node ...
*/
phv = makeNode(PlaceHolderVar);
memcpy(phv, node, sizeof(PlaceHolderVar));
/* ... and replace the copy's phnullingrels field */
phv->phnullingrels = newnullingrels;
return (Node *) phv;
}
/* Otherwise fall through to copy the PlaceHolderVar normally */
}
else if (IsA(node, Query))
{
/* Recurse into RTE or sublink subquery */
Query *newnode;
context->sublevels_up++;
newnode = query_tree_mutator((Query *) node,
add_nulling_relids_mutator,
(void *) context,
0);
context->sublevels_up--;
return (Node *) newnode;
}
return expression_tree_mutator(node, add_nulling_relids_mutator,
(void *) context);
}
/*
* remove_nulling_relids() removes mentions of the specified RT index(es)
* in Var.varnullingrels and PlaceHolderVar.phnullingrels fields within
* the given expression, except in nodes belonging to rels listed in
* except_relids.
*/
Node *
remove_nulling_relids(Node *node,
const Bitmapset *removable_relids,
const Bitmapset *except_relids)
{
remove_nulling_relids_context context;
context.removable_relids = removable_relids;
context.except_relids = except_relids;
context.sublevels_up = 0;
return query_or_expression_tree_mutator(node,
remove_nulling_relids_mutator,
&context,
0);
}
static Node *
remove_nulling_relids_mutator(Node *node,
remove_nulling_relids_context *context)
{
if (node == NULL)
return NULL;
if (IsA(node, Var))
{
Var *var = (Var *) node;
if (var->varlevelsup == context->sublevels_up &&
!bms_is_member(var->varno, context->except_relids) &&
bms_overlap(var->varnullingrels, context->removable_relids))
{
Relids newnullingrels = bms_difference(var->varnullingrels,
context->removable_relids);
/* Micro-optimization: ensure nullingrels is NULL if empty */
if (bms_is_empty(newnullingrels))
newnullingrels = NULL;
/* Copy the Var ... */
var = copyObject(var);
/* ... and replace the copy's varnullingrels field */
var->varnullingrels = newnullingrels;
return (Node *) var;
}
/* Otherwise fall through to copy the Var normally */
}
else if (IsA(node, PlaceHolderVar))
{
PlaceHolderVar *phv = (PlaceHolderVar *) node;
if (phv->phlevelsup == context->sublevels_up &&
!bms_overlap(phv->phrels, context->except_relids))
{
Relids newnullingrels = bms_difference(phv->phnullingrels,
context->removable_relids);
/*
* Micro-optimization: ensure nullingrels is NULL if empty.
*
* Note: it might seem desirable to remove the PHV altogether if
* phnullingrels goes to empty. Currently we dare not do that
* because we use PHVs in some cases to enforce separate identity
* of subexpressions; see wrap_non_vars usages in prepjointree.c.
*/
if (bms_is_empty(newnullingrels))
newnullingrels = NULL;
/* Copy the PlaceHolderVar and mutate what's below ... */
phv = (PlaceHolderVar *)
expression_tree_mutator(node,
remove_nulling_relids_mutator,
(void *) context);
/* ... and replace the copy's phnullingrels field */
phv->phnullingrels = newnullingrels;
/* We must also update phrels, if it contains a removable RTI */
phv->phrels = bms_difference(phv->phrels,
context->removable_relids);
Assert(!bms_is_empty(phv->phrels));
return (Node *) phv;
}
/* Otherwise fall through to copy the PlaceHolderVar normally */
}
else if (IsA(node, Query))
{
/* Recurse into RTE or sublink subquery */
Query *newnode;
context->sublevels_up++;
newnode = query_tree_mutator((Query *) node,
remove_nulling_relids_mutator,
(void *) context,
0);
context->sublevels_up--;
return (Node *) newnode;
}
return expression_tree_mutator(node, remove_nulling_relids_mutator,
(void *) context);
}
/*
* replace_rte_variables() finds all Vars in an expression tree
* that reference a particular RTE, and replaces them with substitute

View File

@ -2206,7 +2206,7 @@ rowcomparesel(PlannerInfo *root,
else
{
/*
* Otherwise, it's a join if there's more than one relation used.
* Otherwise, it's a join if there's more than one base relation used.
*/
is_join_clause = (NumRelids(root, (Node *) opargs) > 1);
}

View File

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202301232
#define CATALOG_VERSION_NO 202301301
#endif

View File

@ -1090,6 +1090,14 @@ typedef struct RangeTblEntry
* alias Vars are generated only for merged columns). We keep these
* entries only because they're needed in expandRTE() and similar code.
*
* Vars appearing within joinaliasvars are marked with varnullingrels sets
* that describe the nulling effects of this join and lower ones. This is
* essential for FULL JOIN cases, because the COALESCE expression only
* describes the semantics correctly if its inputs have been nulled by the
* join. For other cases, it allows expandRTE() to generate a valid
* representation of the join's output without consulting additional
* parser state.
*
* Within a Query loaded from a stored rule, it is possible for non-merged
* joinaliasvars items to be null pointers, which are placeholders for
* (necessarily unreferenced) columns dropped since the rule was made.

View File

@ -249,13 +249,25 @@ struct PlannerInfo
struct AppendRelInfo **append_rel_array pg_node_attr(read_write_ignore);
/*
* all_baserels is a Relids set of all base relids (but not "other"
* relids) in the query; that is, the Relids identifier of the final join
* we need to form. This is computed in make_one_rel, just before we
* start making Paths.
* all_baserels is a Relids set of all base relids (but not joins or
* "other" rels) in the query. This is computed in deconstruct_jointree.
*/
Relids all_baserels;
/*
* outer_join_rels is a Relids set of all outer-join relids in the query.
* This is computed in deconstruct_jointree.
*/
Relids outer_join_rels;
/*
* all_query_rels is a Relids set of all base relids and outer join relids
* (but not "other" relids) in the query. This is the Relids identifier
* of the final join we need to form. This is computed in
* deconstruct_jointree.
*/
Relids all_query_rels;
/*
* nullable_baserels is a Relids set of base relids that are nullable by
* some outer join in the jointree; these are rels that are potentially
@ -313,25 +325,28 @@ struct PlannerInfo
List *canon_pathkeys;
/*
* list of RestrictInfos for mergejoinable outer join clauses
* list of OuterJoinClauseInfos for mergejoinable outer join clauses
* w/nonnullable var on left
*/
List *left_join_clauses;
/*
* list of RestrictInfos for mergejoinable outer join clauses
* list of OuterJoinClauseInfos for mergejoinable outer join clauses
* w/nonnullable var on right
*/
List *right_join_clauses;
/*
* list of RestrictInfos for mergejoinable full join clauses
* list of OuterJoinClauseInfos for mergejoinable full join clauses
*/
List *full_join_clauses;
/* list of SpecialJoinInfos */
List *join_info_list;
/* counter for assigning RestrictInfo serial numbers */
int last_rinfo_serial;
/*
* all_result_relids is empty for SELECT, otherwise it contains at least
* parse->resultRelation. For UPDATE/DELETE/MERGE across an inheritance
@ -592,9 +607,10 @@ typedef struct PartitionSchemeData *PartitionScheme;
* or the output of a sub-SELECT or function that appears in the range table.
* In either case it is uniquely identified by an RT index. A "joinrel"
* is the joining of two or more base rels. A joinrel is identified by
* the set of RT indexes for its component baserels. We create RelOptInfo
* nodes for each baserel and joinrel, and store them in the PlannerInfo's
* simple_rel_array and join_rel_list respectively.
* the set of RT indexes for its component baserels, along with RT indexes
* for any outer joins it has computed. We create RelOptInfo nodes for each
* baserel and joinrel, and store them in the PlannerInfo's simple_rel_array
* and join_rel_list respectively.
*
* Note that there is only one joinrel for any given set of component
* baserels, no matter what order we assemble them in; so an unordered
@ -633,8 +649,10 @@ typedef struct PartitionSchemeData *PartitionScheme;
* Parts of this data structure are specific to various scan and join
* mechanisms. It didn't seem worth creating new node types for them.
*
* relids - Set of base-relation identifiers; it is a base relation
* if there is just one, a join relation if more than one
* relids - Set of relation identifiers (RT indexes). This is a base
* relation if there is just one, a join relation if more;
* in the join case, RT indexes of any outer joins formed
* at or below this join are included along with baserels
* rows - estimated number of tuples in the relation after restriction
* clauses have been applied (ie, output rows of a plan for it)
* consider_startup - true if there is any value in keeping plain paths for
@ -846,7 +864,7 @@ typedef struct RelOptInfo
RelOptKind reloptkind;
/*
* all relations included in this RelOptInfo; set of base relids
* all relations included in this RelOptInfo; set of base + OJ relids
* (rangetable indexes)
*/
Relids relids;
@ -911,7 +929,7 @@ typedef struct RelOptInfo
int32 *attr_widths pg_node_attr(read_write_ignore);
/* LATERAL Vars and PHVs referenced by rel */
List *lateral_vars;
/* rels that reference me laterally */
/* rels that reference this baserel laterally */
Relids lateral_referencers;
/* list of IndexOptInfo */
List *indexlist;
@ -921,10 +939,7 @@ typedef struct RelOptInfo
BlockNumber pages;
Cardinality tuples;
double allvisfrac;
/*
* Indexes in PlannerInfo's eq_classes list of ECs that mention this rel
*/
/* indexes in PlannerInfo's eq_classes list of ECs that mention this rel */
Bitmapset *eclass_indexes;
PlannerInfo *subroot; /* if subquery */
List *subplan_params; /* if subquery */
@ -1378,6 +1393,8 @@ typedef struct EquivalenceMember
bool em_is_const; /* expression is pseudoconstant? */
bool em_is_child; /* derived version for a child relation? */
Oid em_datatype; /* the "nominal type" used by the opfamily */
/* if em_is_child is true, this links to corresponding EM for top parent */
struct EquivalenceMember *em_parent pg_node_attr(read_write_ignore);
} EquivalenceMember;
/*
@ -1484,7 +1501,13 @@ typedef struct PathTarget
* Note: ppi_clauses is only used in ParamPathInfos for base relation paths;
* in join cases it's NIL because the set of relevant clauses varies depending
* on how the join is formed. The relevant clauses will appear in each
* parameterized join path's joinrestrictinfo list, instead.
* parameterized join path's joinrestrictinfo list, instead. ParamPathInfos
* for append relations don't bother with this, either.
*
* ppi_serials is the set of rinfo_serial numbers for quals that are enforced
* by this path. As with ppi_clauses, it's only maintained for baserels.
* (We could construct it on-the-fly from ppi_clauses, but it seems better
* to materialize a copy.)
*/
typedef struct ParamPathInfo
{
@ -1495,6 +1518,7 @@ typedef struct ParamPathInfo
Relids ppi_req_outer; /* rels supplying parameters used by path */
Cardinality ppi_rows; /* estimated number of result tuples */
List *ppi_clauses; /* join clauses available from outer rels */
Bitmapset *ppi_serials; /* set of rinfo_serial for enforced quals */
} ParamPathInfo;
@ -2319,17 +2343,17 @@ typedef struct LimitPath
* If a restriction clause references a single base relation, it will appear
* in the baserestrictinfo list of the RelOptInfo for that base rel.
*
* If a restriction clause references more than one base rel, it will
* If a restriction clause references more than one base+OJ relation, it will
* appear in the joininfo list of every RelOptInfo that describes a strict
* subset of the base rels mentioned in the clause. The joininfo lists are
* subset of the relations mentioned in the clause. The joininfo lists are
* used to drive join tree building by selecting plausible join candidates.
* The clause cannot actually be applied until we have built a join rel
* containing all the base rels it references, however.
* containing all the relations it references, however.
*
* When we construct a join rel that includes all the base rels referenced
* When we construct a join rel that includes all the relations referenced
* in a multi-relation restriction clause, we place that clause into the
* joinrestrictinfo lists of paths for the join rel, if neither left nor
* right sub-path includes all base rels referenced in the clause. The clause
* right sub-path includes all relations referenced in the clause. The clause
* will be applied at that join level, and will not propagate any further up
* the join tree. (Note: the "predicate migration" code was once intended to
* push restriction clauses up and down the plan tree based on evaluation
@ -2350,12 +2374,14 @@ typedef struct LimitPath
* or join to enforce that all members of each EquivalenceClass are in fact
* equal in all rows emitted by the scan or join.
*
* When dealing with outer joins we have to be very careful about pushing qual
* clauses up and down the tree. An outer join's own JOIN/ON conditions must
* be evaluated exactly at that join node, unless they are "degenerate"
* conditions that reference only Vars from the nullable side of the join.
* Quals appearing in WHERE or in a JOIN above the outer join cannot be pushed
* down below the outer join, if they reference any nullable Vars.
* The clause_relids field lists the base plus outer-join RT indexes that
* actually appear in the clause. required_relids lists the minimum set of
* relids needed to evaluate the clause; while this is often equal to
* clause_relids, it can be more. We will add relids to required_relids when
* we need to force an outer join ON clause to be evaluated exactly at the
* level of the outer join, which is true except when it is a "degenerate"
* condition that references only Vars from the nullable side of the join.
*
* RestrictInfo nodes contain a flag to indicate whether a qual has been
* pushed down to a lower level than its original syntactic placement in the
* join tree would suggest. If an outer join prevents us from pushing a qual
@ -2440,6 +2466,12 @@ typedef struct LimitPath
* or merge or hash join clause, so it's of no interest to large parts of
* the planner.
*
* When we generate multiple versions of a clause so as to have versions
* that will work after commuting some left joins per outer join identity 3,
* we mark the one with the fewest nullingrels bits with has_clone = true,
* and the rest with is_clone = true. This allows proper filtering of
* these redundant clauses, so that we apply only one version of them.
*
* When join clauses are generated from EquivalenceClasses, there may be
* several equally valid ways to enforce join equivalence, of which we need
* apply only one. We mark clauses of this kind by setting parent_ec to
@ -2474,16 +2506,23 @@ typedef struct RestrictInfo
/* see comment above */
bool pseudoconstant pg_node_attr(equal_ignore);
/* see comment above */
bool has_clone;
bool is_clone;
/* true if known to contain no leaked Vars */
bool leakproof pg_node_attr(equal_ignore);
/* to indicate if clause contains any volatile functions. */
/* indicates if clause contains any volatile functions */
VolatileFunctionStatus has_volatile pg_node_attr(equal_ignore);
/* see comment above */
Index security_level;
/* The set of relids (varnos) actually referenced in the clause: */
/* number of base rels in clause_relids */
int num_base_rels pg_node_attr(equal_ignore);
/* The relids (varnos+varnullingrels) actually referenced in the clause: */
Relids clause_relids pg_node_attr(equal_ignore);
/* The set of relids required to evaluate the clause: */
@ -2508,6 +2547,25 @@ typedef struct RestrictInfo
*/
Expr *orclause pg_node_attr(equal_ignore);
/*----------
* Serial number of this RestrictInfo. This is unique within the current
* PlannerInfo context, with a few critical exceptions:
* 1. When we generate multiple clones of the same qual condition to
* cope with outer join identity 3, all the clones get the same serial
* number. This reflects that we only want to apply one of them in any
* given plan.
* 2. If we manufacture a commuted version of a qual to use as an index
* condition, it copies the original's rinfo_serial, since it is in
* practice the same condition.
* 3. RestrictInfos made for a child relation copy their parent's
* rinfo_serial. Likewise, when an EquivalenceClass makes a derived
* equality clause for a child relation, it copies the rinfo_serial of
* the matching equality clause for the parent. This allows detection
* of redundant pushed-down equality clauses.
*----------
*/
int rinfo_serial;
/*
* Generating EquivalenceClass. This field is NULL unless clause is
* potentially redundant.
@ -2624,10 +2682,15 @@ typedef struct MergeScanSelCache
* of a plan tree. This is used during planning to represent the contained
* expression. At the end of the planning process it is replaced by either
* the contained expression or a Var referring to a lower-level evaluation of
* the contained expression. Typically the evaluation occurs below an outer
* the contained expression. Generally the evaluation occurs below an outer
* join, and Var references above the outer join might thereby yield NULL
* instead of the expression value.
*
* phrels and phlevelsup correspond to the varno/varlevelsup fields of a
* plain Var, except that phrels has to be a relid set since the evaluation
* level of a PlaceHolderVar might be a join rather than a base relation.
* Likewise, phnullingrels corresponds to varnullingrels.
*
* Although the planner treats this as an expression node type, it is not
* recognized by the parser or executor, so we declare it here rather than
* in primnodes.h.
@ -2640,8 +2703,10 @@ typedef struct MergeScanSelCache
* PHV. Another way in which it can happen is that initplan sublinks
* could get replaced by differently-numbered Params when sublink folding
* is done. (The end result of such a situation would be some
* unreferenced initplans, which is annoying but not really a problem.) On
* the same reasoning, there is no need to examine phrels.
* unreferenced initplans, which is annoying but not really a problem.)
* On the same reasoning, there is no need to examine phrels. But we do
* need to compare phnullingrels, as that represents effects that are
* external to the original value of the PHV.
*/
typedef struct PlaceHolderVar
@ -2651,9 +2716,12 @@ typedef struct PlaceHolderVar
/* the represented expression */
Expr *phexpr pg_node_attr(equal_ignore);
/* base relids syntactically within expr src */
/* base+OJ relids syntactically within expr src */
Relids phrels pg_node_attr(equal_ignore);
/* RT indexes of outer joins that can null PHV's value */
Relids phnullingrels;
/* ID for PHV (unique within planner run) */
Index phid;
@ -2677,20 +2745,49 @@ typedef struct PlaceHolderVar
* We make SpecialJoinInfos for FULL JOINs even though there is no flexibility
* of planning for them, because this simplifies make_join_rel()'s API.
*
* min_lefthand and min_righthand are the sets of base relids that must be
* available on each side when performing the special join. lhs_strict is
* true if the special join's condition cannot succeed when the LHS variables
* are all NULL (this means that an outer join can commute with upper-level
* outer joins even if it appears in their RHS). We don't bother to set
* lhs_strict for FULL JOINs, however.
*
* min_lefthand and min_righthand are the sets of base+OJ relids that must be
* available on each side when performing the special join.
* It is not valid for either min_lefthand or min_righthand to be empty sets;
* if they were, this would break the logic that enforces join order.
*
* syn_lefthand and syn_righthand are the sets of base relids that are
* syn_lefthand and syn_righthand are the sets of base+OJ relids that are
* syntactically below this special join. (These are needed to help compute
* min_lefthand and min_righthand for higher joins.)
*
* jointype is never JOIN_RIGHT; a RIGHT JOIN is handled by switching
* the inputs to make it a LEFT JOIN. So the allowed values of jointype
* in a join_info_list member are only LEFT, FULL, SEMI, or ANTI.
*
* ojrelid is the RT index of the join RTE representing this outer join,
* if there is one. It is zero when jointype is INNER or SEMI, and can be
* zero for jointype ANTI (if the join was transformed from a SEMI join).
* One use for this field is that when constructing the output targetlist of a
* join relation that implements this OJ, we add ojrelid to the varnullingrels
* and phnullingrels fields of nullable (RHS) output columns, so that the
* output Vars and PlaceHolderVars correctly reflect the nulling that has
* potentially happened to them.
*
* commute_above_l is filled with the relids of syntactically-higher outer
* joins that have been found to commute with this one per outer join identity
* 3 (see optimizer/README), when this join is in the LHS of the upper join
* (so, this is the lower join in the first form of the identity).
*
* commute_above_r is filled with the relids of syntactically-higher outer
* joins that have been found to commute with this one per outer join identity
* 3, when this join is in the RHS of the upper join (so, this is the lower
* join in the second form of the identity).
*
* commute_below is filled with the relids of syntactically-lower outer joins
* that have been found to commute with this one per outer join identity 3.
* (We need not record which side they are on, since that can be determined
* by seeing whether the lower join's relid appears in syn_lefthand or
* syn_righthand.)
*
* lhs_strict is true if the special join's condition cannot succeed when the
* LHS variables are all NULL (this means that an outer join can commute with
* upper-level outer joins even if it appears in their RHS). We don't bother
* to set lhs_strict for FULL JOINs, however.
*
* delay_upper_joins is set true if we detect a pushed-down clause that has
* to be evaluated after this join is formed (because it references the RHS).
* Any outer joins that have such a clause and this join in their RHS cannot
@ -2705,10 +2802,6 @@ typedef struct PlaceHolderVar
* join planning; but it's helpful to have it available during planning of
* parameterized table scans, so we store it in the SpecialJoinInfo structs.)
*
* jointype is never JOIN_RIGHT; a RIGHT JOIN is handled by switching
* the inputs to make it a LEFT JOIN. So the allowed values of jointype
* in a join_info_list member are only LEFT, FULL, SEMI, or ANTI.
*
* For purposes of join selectivity estimation, we create transient
* SpecialJoinInfo structures for regular inner joins; so it is possible
* to have jointype == JOIN_INNER in such a structure, even though this is
@ -2728,11 +2821,15 @@ struct SpecialJoinInfo
pg_node_attr(no_read)
NodeTag type;
Relids min_lefthand; /* base relids in minimum LHS for join */
Relids min_righthand; /* base relids in minimum RHS for join */
Relids syn_lefthand; /* base relids syntactically within LHS */
Relids syn_righthand; /* base relids syntactically within RHS */
Relids min_lefthand; /* base+OJ relids in minimum LHS for join */
Relids min_righthand; /* base+OJ relids in minimum RHS for join */
Relids syn_lefthand; /* base+OJ relids syntactically within LHS */
Relids syn_righthand; /* base+OJ relids syntactically within RHS */
JoinType jointype; /* always INNER, LEFT, FULL, SEMI, or ANTI */
Index ojrelid; /* outer join's RT index; 0 if none */
Relids commute_above_l; /* commuting OJs above this one, if LHS */
Relids commute_above_r; /* commuting OJs above this one, if RHS */
Relids commute_below; /* commuting OJs below this one */
bool lhs_strict; /* joinclause is strict for some LHS rel */
bool delay_upper_joins; /* can't commute with upper RHS */
/* Remaining fields are set only for JOIN_SEMI jointype: */
@ -2742,6 +2839,21 @@ struct SpecialJoinInfo
List *semi_rhs_exprs; /* righthand-side expressions of these ops */
};
/*
* Transient outer-join clause info.
*
* We set aside every outer join ON clause that looks mergejoinable,
* and process it specially at the end of qual distribution.
*/
typedef struct OuterJoinClauseInfo
{
pg_node_attr(no_copy_equal, no_read)
NodeTag type;
RestrictInfo *rinfo; /* a mergejoinable outer-join clause */
SpecialJoinInfo *sjinfo; /* the outer join's SpecialJoinInfo */
} OuterJoinClauseInfo;
/*
* Append-relation info.
*

View File

@ -695,6 +695,7 @@ typedef struct WorkTableScan
* When the plan node represents a foreign join, scan.scanrelid is zero and
* fs_relids must be consulted to identify the join relation. (fs_relids
* is valid for simple scans as well, but will always match scan.scanrelid.)
* fs_relids includes outer joins; fs_base_relids does not.
*
* If the FDW's PlanDirectModify() callback decides to repurpose a ForeignScan
* node to perform the UPDATE or DELETE operation directly in the remote
@ -716,7 +717,8 @@ typedef struct ForeignScan
List *fdw_private; /* private data for FDW */
List *fdw_scan_tlist; /* optional tlist describing scan tuple */
List *fdw_recheck_quals; /* original quals not in scan.plan.qual */
Bitmapset *fs_relids; /* RTIs generated by this scan */
Bitmapset *fs_relids; /* base+OJ RTIs generated by this scan */
Bitmapset *fs_base_relids; /* base RTIs generated by this scan */
bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan;

View File

@ -190,6 +190,14 @@ typedef struct Expr
* row identity information during UPDATE/DELETE/MERGE. This value should
* never be seen outside the planner.
*
* 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.
*
* varlevelsup is greater than zero in Vars that represent outer references.
* Note that it affects the meaning of all of varno, varnullingrels, and
* varnosyn, all of which refer to the range table of that query level.
*
* In the parser, varnosyn and varattnosyn are either identical to
* varno/varattno, or they specify the column's position in an aliased JOIN
* RTE that hides the semantic referent RTE's refname. This is a syntactic
@ -232,6 +240,8 @@ typedef struct Var
int32 vartypmod;
/* OID of collation, or InvalidOid if none */
Oid varcollid;
/* RT indexes of outer joins that can replace the Var's value with null */
Bitmapset *varnullingrels;
/*
* for subquery variables referencing outer relations; 0 in a normal var,

View File

@ -197,6 +197,6 @@ extern bool contain_var_clause(Node *node);
extern bool contain_vars_of_level(Node *node, int levelsup);
extern int locate_var_of_level(Node *node, int levelsup);
extern List *pull_var_clause(Node *node, int flags);
extern Node *flatten_join_alias_vars(Query *query, Node *node);
extern Node *flatten_join_alias_vars(PlannerInfo *root, Query *query, Node *node);
#endif /* OPTIMIZER_H */

View File

@ -304,6 +304,7 @@ extern void expand_planner_arrays(PlannerInfo *root, int add_size);
extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
RelOptInfo *parent);
extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
extern RelOptInfo *find_base_rel_ignore_join(PlannerInfo *root, int relid);
extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
extern RelOptInfo *build_join_rel(PlannerInfo *root,
Relids joinrelids,
@ -332,9 +333,10 @@ extern ParamPathInfo *get_appendrel_parampathinfo(RelOptInfo *appendrel,
Relids required_outer);
extern ParamPathInfo *find_param_path_info(RelOptInfo *rel,
Relids required_outer);
extern Bitmapset *get_param_path_clause_serials(Path *path);
extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
RelOptInfo *parent_joinrel, List *restrictlist,
SpecialJoinInfo *sjinfo, JoinType jointype);
SpecialJoinInfo *sjinfo);
#endif /* PATHNODE_H */

View File

@ -27,6 +27,9 @@ extern void update_placeholder_eval_levels(PlannerInfo *root,
extern void fix_placeholder_input_needed_levels(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);
RelOptInfo *outer_rel, RelOptInfo *inner_rel,
SpecialJoinInfo *sjinfo);
extern bool contain_placeholder_references_to(PlannerInfo *root, Node *clause,
int relid);
#endif /* PLACEHOLDER_H */

View File

@ -29,7 +29,8 @@ extern void pull_up_subqueries(PlannerInfo *root);
extern void flatten_simple_union_all(PlannerInfo *root);
extern void reduce_outer_joins(PlannerInfo *root);
extern void remove_useless_result_rtes(PlannerInfo *root);
extern Relids get_relids_in_jointree(Node *jtnode, bool include_joins);
extern Relids get_relids_in_jointree(Node *jtnode, bool include_outer_joins,
bool include_inner_joins);
extern Relids get_relids_for_join(Query *query, int joinrelid);
/*

View File

@ -41,6 +41,9 @@ extern void extract_actual_join_clauses(List *restrictinfo_list,
Relids joinrelids,
List **joinquals,
List **otherquals);
extern bool clause_is_computable_at(PlannerInfo *root,
Relids clause_relids,
Relids eval_relids);
extern bool join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel);
extern bool join_clause_is_movable_into(RestrictInfo *rinfo,
Relids currentrelids,

View File

@ -118,6 +118,13 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
* This is one-for-one with p_rtable, but contains NULLs for non-join
* RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
*
* p_nullingrels: list of Bitmapsets associated with p_rtable entries, each
* containing the set of outer-join RTE indexes that can null that relation
* at the current point in the parse tree. This is one-for-one with p_rtable,
* but may be shorter than p_rtable, in which case the missing entries are
* implicitly empty (NULL). That rule allows us to save work when the query
* contains no outer joins.
*
* p_joinlist: list of join items (RangeTblRef and JoinExpr nodes) that
* will become the fromlist of the query's top-level FromExpr node.
*
@ -187,6 +194,7 @@ struct ParseState
List *p_rteperminfos; /* list of RTEPermissionInfo nodes for each
* RTE_RELATION entry in rtable */
List *p_joinexprs; /* JoinExprs for RTE_JOIN p_rtable entries */
List *p_nullingrels; /* Bitmapsets showing nulling outer joins */
List *p_joinlist; /* join items so far (will become FromExpr
* node's fromlist) */
List *p_namespace; /* currently-referenceable RTEs (List of

View File

@ -41,6 +41,7 @@ extern Node *scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
int location);
extern Node *colNameToVar(ParseState *pstate, const char *colname, bool localonly,
int location);
extern void markNullableIfNeeded(ParseState *pstate, Var *var);
extern void markVarForSelectPriv(ParseState *pstate, Var *var);
extern Relation parserOpenTable(ParseState *pstate, const RangeVar *relation,
int lockmode);
@ -113,7 +114,7 @@ extern void errorMissingColumn(ParseState *pstate,
extern void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
int location, bool include_dropped,
List **colnames, List **colvars);
extern List *expandNSItemVars(ParseNamespaceItem *nsitem,
extern List *expandNSItemVars(ParseState *pstate, ParseNamespaceItem *nsitem,
int sublevels_up, int location,
List **colnames);
extern List *expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,

View File

@ -65,6 +65,13 @@ extern bool contain_windowfuncs(Node *node);
extern int locate_windowfunc(Node *node);
extern bool checkExprHasSubLink(Node *node);
extern Node *add_nulling_relids(Node *node,
const Bitmapset *target_relids,
const Bitmapset *added_relids);
extern Node *remove_nulling_relids(Node *node,
const Bitmapset *removable_relids,
const Bitmapset *except_relids);
extern Node *replace_rte_variables(Node *node,
int target_varno, int sublevels_up,
replace_rte_variables_callback callback,

View File

@ -1370,8 +1370,8 @@ drop table p_t1;
--
-- Test GROUP BY matching of join columns that are type-coerced due to USING
--
create temp table t1(f1 int, f2 bigint);
create temp table t2(f1 bigint, f22 bigint);
create temp table t1(f1 int, f2 int);
create temp table t2(f1 bigint, f2 oid);
select f1 from t1 left join t2 using (f1) group by f1;
f1
----
@ -1392,6 +1392,22 @@ select t1.f1 from t1 left join t2 using (f1) group by f1;
ERROR: column "t1.f1" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: select t1.f1 from t1 left join t2 using (f1) group by f1;
^
-- check case where we have to inject nullingrels into coerced join alias
select f1, count(*) from
t1 x(x0,x1) left join (t1 left join t2 using(f1)) on (x0 = 0)
group by f1;
f1 | count
----+-------
(0 rows)
-- same, for a RelabelType coercion
select f2, count(*) from
t1 x(x0,x1) left join (t1 left join t2 using(f2)) on (x0 = 0)
group by f2;
f2 | count
----+-------
(0 rows)
drop table t1, t2;
--
-- Test planner's selection of pathkeys for ORDER BY aggregates

View File

@ -2335,17 +2335,17 @@ select a.f1, b.f1, t.thousand, t.tenthous from
(select sum(f1)+1 as f1 from int4_tbl i4a) a,
(select sum(f1) as f1 from int4_tbl i4b) b
where b.f1 = t.thousand and a.f1 = b.f1 and (a.f1+b.f1+999) = t.tenthous;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Nested Loop
-> Aggregate
-> Seq Scan on int4_tbl i4b
-> Nested Loop
Join Filter: ((sum(i4b.f1)) = ((sum(i4a.f1) + 1)))
-> Aggregate
-> Seq Scan on int4_tbl i4a
-> Index Only Scan using tenk1_thous_tenthous on tenk1 t
Index Cond: ((thousand = (sum(i4b.f1))) AND (tenthous = ((((sum(i4a.f1) + 1)) + (sum(i4b.f1))) + 999)))
-> Aggregate
-> Seq Scan on int4_tbl i4b
-> Index Only Scan using tenk1_thous_tenthous on tenk1 t
Index Cond: ((thousand = (sum(i4b.f1))) AND (tenthous = ((((sum(i4a.f1) + 1)) + (sum(i4b.f1))) + 999)))
(9 rows)
select a.f1, b.f1, t.thousand, t.tenthous from
@ -4138,6 +4138,60 @@ using (join_key);
1 | |
(2 rows)
--
-- check handling of a variable-free join alias
--
explain (verbose, costs off)
select * from
int4_tbl i0 left join
( (select *, 123 as x from int4_tbl i1) ss1
left join
(select *, q2 as x from int8_tbl i2) ss2
using (x)
) ss0
on (i0.f1 = ss0.f1)
order by i0.f1, x;
QUERY PLAN
-------------------------------------------------------------
Sort
Output: i0.f1, ('123'::bigint), i1.f1, i2.q1, i2.q2
Sort Key: i0.f1, ('123'::bigint)
-> Hash Right Join
Output: i0.f1, ('123'::bigint), i1.f1, i2.q1, i2.q2
Hash Cond: (i1.f1 = i0.f1)
-> Nested Loop Left Join
Output: i1.f1, i2.q1, i2.q2, '123'::bigint
-> Seq Scan on public.int4_tbl i1
Output: i1.f1
-> Materialize
Output: i2.q1, i2.q2
-> Seq Scan on public.int8_tbl i2
Output: i2.q1, i2.q2
Filter: (123 = i2.q2)
-> Hash
Output: i0.f1
-> Seq Scan on public.int4_tbl i0
Output: i0.f1
(19 rows)
select * from
int4_tbl i0 left join
( (select *, 123 as x from int4_tbl i1) ss1
left join
(select *, q2 as x from int8_tbl i2) ss2
using (x)
) ss0
on (i0.f1 = ss0.f1)
order by i0.f1, x;
f1 | x | f1 | q1 | q2
-------------+-----+-------------+------------------+-----
-2147483647 | 123 | -2147483647 | 4567890123456789 | 123
-123456 | 123 | -123456 | 4567890123456789 | 123
0 | 123 | 0 | 4567890123456789 | 123
123456 | 123 | 123456 | 4567890123456789 | 123
2147483647 | 123 | 2147483647 | 4567890123456789 | 123
(5 rows)
--
-- test successful handling of nested outer joins with degenerate join quals
--
@ -4727,6 +4781,103 @@ select a.unique1, b.unique2
123 | 123
(1 row)
--
-- test full-join strength reduction
--
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42;
QUERY PLAN
----------------------------------------------------
Nested Loop Left Join
Join Filter: (a.unique1 = b.unique2)
-> Index Only Scan using onek_unique1 on onek a
Index Cond: (unique1 = 42)
-> Index Only Scan using onek_unique2 on onek b
Index Cond: (unique2 = 42)
(6 rows)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42;
unique1 | unique2
---------+---------
42 | 42
(1 row)
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where b.unique2 = 43;
QUERY PLAN
----------------------------------------------------
Nested Loop Left Join
Join Filter: (a.unique1 = b.unique2)
-> Index Only Scan using onek_unique2 on onek b
Index Cond: (unique2 = 43)
-> Index Only Scan using onek_unique1 on onek a
Index Cond: (unique1 = 43)
(6 rows)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where b.unique2 = 43;
unique1 | unique2
---------+---------
43 | 43
(1 row)
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42 and b.unique2 = 42;
QUERY PLAN
----------------------------------------------------
Nested Loop
-> Index Only Scan using onek_unique1 on onek a
Index Cond: (unique1 = 42)
-> Index Only Scan using onek_unique2 on onek b
Index Cond: (unique2 = 42)
(5 rows)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42 and b.unique2 = 42;
unique1 | unique2
---------+---------
42 | 42
(1 row)
--
-- test result-RTE removal underneath a full join
--
explain (costs off)
select * from
(select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1
full join
(select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2
on true;
QUERY PLAN
--------------------------------------
Merge Full Join
-> Seq Scan on int8_tbl i81
Filter: (q2 = 123)
-> Materialize
-> Seq Scan on int8_tbl i82
Filter: (q2 = 456)
(6 rows)
select * from
(select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1
full join
(select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2
on true;
q1 | q2 | v1 | v2 | v1 | v2 | q1 | q2
------------------+-----+-----+----+-----+----+-----+-----
4567890123456789 | 123 | 123 | 2 | 456 | 2 | 123 | 456
(1 row)
--
-- test join removal
--

View File

@ -304,7 +304,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t2.b FROM prt2 t2 WHERE t2.a = 0)
-> Seq Scan on prt2_p2 t2_2
Filter: (a = 0)
-> Nested Loop Semi Join
Join Filter: (t2_3.b = t1_3.a)
Join Filter: (t1_3.a = t2_3.b)
-> Seq Scan on prt1_p3 t1_3
Filter: (b = 0)
-> Materialize
@ -601,7 +601,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
Sort Key: t1.a
-> Append
-> Nested Loop
Join Filter: (((t3_1.a + t3_1.b) / 2) = t1_1.a)
Join Filter: (t1_1.a = ((t3_1.a + t3_1.b) / 2))
-> Hash Join
Hash Cond: (t2_1.b = t1_1.a)
-> Seq Scan on prt2_p1 t2_1
@ -611,7 +611,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Index Scan using iprt1_e_p1_ab2 on prt1_e_p1 t3_1
Index Cond: (((a + b) / 2) = t2_1.b)
-> Nested Loop
Join Filter: (((t3_2.a + t3_2.b) / 2) = t1_2.a)
Join Filter: (t1_2.a = ((t3_2.a + t3_2.b) / 2))
-> Hash Join
Hash Cond: (t2_2.b = t1_2.a)
-> Seq Scan on prt2_p2 t2_2
@ -621,7 +621,7 @@ SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM prt1 t1, prt2 t2, prt1_e t
-> Index Scan using iprt1_e_p2_ab2 on prt1_e_p2 t3_2
Index Cond: (((a + b) / 2) = t2_2.b)
-> Nested Loop
Join Filter: (((t3_3.a + t3_3.b) / 2) = t1_3.a)
Join Filter: (t1_3.a = ((t3_3.a + t3_3.b) / 2))
-> Hash Join
Hash Cond: (t2_3.b = t1_3.a)
-> Seq Scan on prt2_p3 t2_3
@ -926,7 +926,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER
Sort Key: t1.a
-> Append
-> Nested Loop
Join Filter: (t1_5.b = t1_2.a)
Join Filter: (t1_2.a = t1_5.b)
-> HashAggregate
Group Key: t1_5.b
-> Hash Join
@ -939,7 +939,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER
Index Cond: (a = ((t2_1.a + t2_1.b) / 2))
Filter: (b = 0)
-> Nested Loop
Join Filter: (t1_6.b = t1_3.a)
Join Filter: (t1_3.a = t1_6.b)
-> HashAggregate
Group Key: t1_6.b
-> Hash Join
@ -952,7 +952,7 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER
Index Cond: (a = ((t2_2.a + t2_2.b) / 2))
Filter: (b = 0)
-> Nested Loop
Join Filter: (t1_7.b = t1_4.a)
Join Filter: (t1_4.a = t1_7.b)
-> HashAggregate
Group Key: t1_7.b
-> Nested Loop

View File

@ -492,8 +492,8 @@ drop table p_t1;
-- Test GROUP BY matching of join columns that are type-coerced due to USING
--
create temp table t1(f1 int, f2 bigint);
create temp table t2(f1 bigint, f22 bigint);
create temp table t1(f1 int, f2 int);
create temp table t2(f1 bigint, f2 oid);
select f1 from t1 left join t2 using (f1) group by f1;
select f1 from t1 left join t2 using (f1) group by t1.f1;
@ -501,6 +501,16 @@ select t1.f1 from t1 left join t2 using (f1) group by t1.f1;
-- only this one should fail:
select t1.f1 from t1 left join t2 using (f1) group by f1;
-- check case where we have to inject nullingrels into coerced join alias
select f1, count(*) from
t1 x(x0,x1) left join (t1 left join t2 using(f1)) on (x0 = 0)
group by f1;
-- same, for a RelabelType coercion
select f2, count(*) from
t1 x(x0,x1) left join (t1 left join t2 using(f2)) on (x0 = 0)
group by f2;
drop table t1, t2;
--

View File

@ -1409,6 +1409,30 @@ left join
) foo3
using (join_key);
--
-- check handling of a variable-free join alias
--
explain (verbose, costs off)
select * from
int4_tbl i0 left join
( (select *, 123 as x from int4_tbl i1) ss1
left join
(select *, q2 as x from int8_tbl i2) ss2
using (x)
) ss0
on (i0.f1 = ss0.f1)
order by i0.f1, x;
select * from
int4_tbl i0 left join
( (select *, 123 as x from int4_tbl i1) ss1
left join
(select *, q2 as x from int8_tbl i2) ss2
using (x)
) ss0
on (i0.f1 = ss0.f1)
order by i0.f1, x;
--
-- test successful handling of nested outer joins with degenerate join quals
--
@ -1641,6 +1665,54 @@ select a.unique1, b.unique2
from onek a left join onek b on a.unique1 = b.unique2
where b.unique2 = any (select q1 from int8_tbl c where c.q1 < b.unique1);
--
-- test full-join strength reduction
--
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42;
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42;
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where b.unique2 = 43;
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where b.unique2 = 43;
explain (costs off)
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42 and b.unique2 = 42;
select a.unique1, b.unique2
from onek a full join onek b on a.unique1 = b.unique2
where a.unique1 = 42 and b.unique2 = 42;
--
-- test result-RTE removal underneath a full join
--
explain (costs off)
select * from
(select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1
full join
(select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2
on true;
select * from
(select * from int8_tbl i81 join (values(123,2)) v(v1,v2) on q2=v1) ss1
full join
(select * from (values(456,2)) w(v1,v2) join int8_tbl i82 on q2=v1) ss2
on true;
--
-- test join removal
--