Skip to content

Commit 3bb0e0f

Browse files
Alena RybakinaCommitfest Bot
authored andcommitted
Teach the planner to convert EXISTS and NOT EXISTS subqueries into semi and anti joins. To do this, we put all potential expressions from the qual list and join list into the common list and check each expression one by one to see if they are suitable for transformation.
Authors: Alena Rybakina <[email protected]> Reviewed-by: Ranier Vilela <[email protected]>, Ilia Evdokimov <[email protected]>, Peter Petrov <[email protected]>
1 parent ef5b87b commit 3bb0e0f

File tree

3 files changed

+1095
-22
lines changed

3 files changed

+1095
-22
lines changed

src/backend/optimizer/plan/subselect.c

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,10 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
14591459
int varno;
14601460
Relids clause_varnos;
14611461
Relids upper_varnos;
1462+
ListCell *lc;
1463+
List *newWhere = NIL;
1464+
List *all_clauses = NIL;
1465+
bool upper_reference_exists = false;
14621466

14631467
Assert(sublink->subLinkType == EXISTS_SUBLINK);
14641468

@@ -1488,32 +1492,71 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
14881492
if (!simplify_EXISTS_query(root, subselect))
14891493
return NULL;
14901494

1491-
/*
1492-
* Separate out the WHERE clause. (We could theoretically also remove
1493-
* top-level plain JOIN/ON clauses, but it's probably not worth the
1494-
* trouble.)
1495-
*/
1496-
whereClause = subselect->jointree->quals;
1495+
if (subselect->jointree->quals)
1496+
all_clauses = lappend(all_clauses, subselect->jointree->quals);
1497+
14971498
subselect->jointree->quals = NULL;
14981499

1499-
/*
1500-
* The rest of the sub-select must not refer to any Vars of the parent
1501-
* query. (Vars of higher levels should be okay, though.)
1502-
*/
1503-
if (contain_vars_of_level((Node *) subselect, 1))
1504-
return NULL;
1500+
/* Gather all clauses in main list for the further consideration */
1501+
all_clauses = list_concat(all_clauses, subselect->jointree->fromlist);
15051502

15061503
/*
1507-
* On the other hand, the WHERE clause must contain some Vars of the
1508-
* parent query, else it's not gonna be a join.
1504+
* We will able to remove top-level plain JOIN/ON clauses if they are not outer join.
15091505
*/
1510-
if (!contain_vars_of_level(whereClause, 1))
1511-
return NULL;
1506+
foreach (lc, all_clauses)
1507+
{
1508+
Node *je = ((Node *) lfirst(lc));
1509+
1510+
whereClause = copyObject(je);
1511+
1512+
if (IsA(whereClause, RangeTblRef))
1513+
continue;
1514+
1515+
if (IsA(whereClause, JoinExpr))
1516+
{
1517+
if (((JoinExpr *) whereClause)->jointype != JOIN_INNER)
1518+
{
1519+
/*
1520+
* Clauses must not refer to any Vars of the parent
1521+
* query. (Vars of higher levels should be okay, though.)
1522+
*/
1523+
if (contain_vars_of_level(whereClause, 1))
1524+
return NULL;
1525+
else
1526+
continue;
1527+
}
1528+
else if (((JoinExpr *) whereClause)->quals != NULL)
1529+
whereClause = ((JoinExpr *) whereClause)->quals;
1530+
}
1531+
1532+
/*
1533+
* We don't risk optimizing if the WHERE clause is volatile, either.
1534+
*/
1535+
if (contain_volatile_functions(whereClause))
1536+
return NULL;
1537+
1538+
/*
1539+
* Clean up the WHERE clause by doing const-simplification etc on it.
1540+
*/
1541+
whereClause = eval_const_expressions(root, whereClause);
1542+
whereClause = (Node *) canonicalize_qual((Expr *) whereClause, false);
1543+
1544+
if(!IsA(whereClause, JoinExpr))
1545+
newWhere = lappend(newWhere, whereClause);
1546+
else
1547+
return NULL;
1548+
1549+
if (contain_vars_of_level((Node *) whereClause, 1))
1550+
upper_reference_exists = true;
1551+
}
1552+
1553+
list_free(all_clauses);
15121554

15131555
/*
1514-
* We don't risk optimizing if the WHERE clause is volatile, either.
1556+
* There are no WHERE clause containing some Vars of the
1557+
* parent query, so it's not gonna be a join.
15151558
*/
1516-
if (contain_volatile_functions(whereClause))
1559+
if(!upper_reference_exists)
15171560
return NULL;
15181561

15191562
/*
@@ -1567,23 +1610,24 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
15671610
*/
15681611
rtoffset = list_length(parse->rtable);
15691612
OffsetVarNodes((Node *) subselect, rtoffset, 0);
1570-
OffsetVarNodes(whereClause, rtoffset, 0);
15711613

15721614
/*
15731615
* Upper-level vars in subquery will now be one level closer to their
15741616
* parent than before; in particular, anything that had been level 1
15751617
* becomes level zero.
15761618
*/
15771619
IncrementVarSublevelsUp((Node *) subselect, -1, 1);
1578-
IncrementVarSublevelsUp(whereClause, -1, 1);
1620+
1621+
OffsetVarNodes((Node *) newWhere, rtoffset, 0);
1622+
IncrementVarSublevelsUp((Node *) newWhere, -1, 1);
15791623

15801624
/*
15811625
* Now that the WHERE clause is adjusted to match the parent query
15821626
* environment, we can easily identify all the level-zero rels it uses.
15831627
* The ones <= rtoffset belong to the upper query; the ones > rtoffset do
15841628
* not.
15851629
*/
1586-
clause_varnos = pull_varnos(root, whereClause);
1630+
clause_varnos = pull_varnos(root, (Node *) newWhere);
15871631
upper_varnos = NULL;
15881632
varno = -1;
15891633
while ((varno = bms_next_member(clause_varnos, varno)) >= 0)
@@ -1601,6 +1645,31 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
16011645
if (!bms_is_subset(upper_varnos, available_rels))
16021646
return NULL;
16031647

1648+
/*
1649+
* In case of a successful attempt, replaces it with the correct condition.
1650+
* We were sure that inner relations are independent, so we confidently
1651+
* can replace their join condition on true.
1652+
*/
1653+
foreach(lc, subselect->jointree->fromlist)
1654+
{
1655+
Node *node = lfirst(lc);
1656+
1657+
if (IsA(node, RangeTblRef))
1658+
continue;
1659+
1660+
if ((IsA(node, JoinExpr) && ((JoinExpr *)node)->jointype != JOIN_INNER))
1661+
continue;
1662+
1663+
if (IsA(node, JoinExpr) && ((JoinExpr *) node)->quals != NULL)
1664+
((JoinExpr *) node)->quals = (Node *) makeConst(BOOLOID,
1665+
-1,
1666+
InvalidOid,
1667+
sizeof(bool),
1668+
(Datum) 1,
1669+
false,
1670+
true);
1671+
}
1672+
16041673
/*
16051674
* Now we can attach the modified subquery rtable to the parent. This also
16061675
* adds subquery's RTEPermissionInfos into the upper query.
@@ -1622,7 +1691,7 @@ convert_EXISTS_sublink_to_join(PlannerInfo *root, SubLink *sublink,
16221691
result->rarg = (Node *) subselect->jointree;
16231692
result->usingClause = NIL;
16241693
result->join_using_alias = NULL;
1625-
result->quals = whereClause;
1694+
result->quals = (Node *) make_ands_explicit(newWhere);
16261695
result->alias = NULL;
16271696
result->rtindex = 0; /* we don't need an RTE for it */
16281697

0 commit comments

Comments
 (0)