@@ -82,13 +82,14 @@ static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
82
82
RangeTblEntry * rte );
83
83
static RelOptInfo * make_rel_from_joinlist (PlannerInfo * root , List * joinlist );
84
84
static bool subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
85
- bool * differentTypes );
85
+ bool * unsafeColumns );
86
86
static bool recurse_pushdown_safe (Node * setOp , Query * topquery ,
87
- bool * differentTypes );
87
+ bool * unsafeColumns );
88
+ static void check_output_expressions (Query * subquery , bool * unsafeColumns );
88
89
static void compare_tlist_datatypes (List * tlist , List * colTypes ,
89
- bool * differentTypes );
90
+ bool * unsafeColumns );
90
91
static bool qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
91
- bool * differentTypes );
92
+ bool * unsafeColumns );
92
93
static void subquery_push_qual (Query * subquery ,
93
94
RangeTblEntry * rte , Index rti , Node * qual );
94
95
static void recurse_push_qual (Node * setOp , Query * topquery ,
@@ -1048,7 +1049,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
1048
1049
Query * parse = root -> parse ;
1049
1050
Query * subquery = rte -> subquery ;
1050
1051
Relids required_outer ;
1051
- bool * differentTypes ;
1052
+ bool * unsafeColumns ;
1052
1053
double tuple_fraction ;
1053
1054
PlannerInfo * subroot ;
1054
1055
List * pathkeys ;
@@ -1067,8 +1068,12 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
1067
1068
*/
1068
1069
required_outer = rel -> lateral_relids ;
1069
1070
1070
- /* We need a workspace for keeping track of set-op type coercions */
1071
- differentTypes = (bool * )
1071
+ /*
1072
+ * We need a workspace for keeping track of unsafe-to-reference columns.
1073
+ * unsafeColumns[i] is set TRUE if we've found that output column i of the
1074
+ * subquery is unsafe to use in a pushed-down qual.
1075
+ */
1076
+ unsafeColumns = (bool * )
1072
1077
palloc0 ((list_length (subquery -> targetList ) + 1 ) * sizeof (bool ));
1073
1078
1074
1079
/*
@@ -1096,7 +1101,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
1096
1101
* push down a pushable qual, because it'd result in a worse plan?
1097
1102
*/
1098
1103
if (rel -> baserestrictinfo != NIL &&
1099
- subquery_is_pushdown_safe (subquery , subquery , differentTypes ))
1104
+ subquery_is_pushdown_safe (subquery , subquery , unsafeColumns ))
1100
1105
{
1101
1106
/* OK to consider pushing down individual quals */
1102
1107
List * upperrestrictlist = NIL ;
@@ -1110,7 +1115,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
1110
1115
if (!rinfo -> pseudoconstant &&
1111
1116
(!rte -> security_barrier ||
1112
1117
!contain_leaky_functions (clause )) &&
1113
- qual_is_pushdown_safe (subquery , rti , clause , differentTypes ))
1118
+ qual_is_pushdown_safe (subquery , rti , clause , unsafeColumns ))
1114
1119
{
1115
1120
/* Push it down */
1116
1121
subquery_push_qual (subquery , rte , rti , clause );
@@ -1124,7 +1129,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
1124
1129
rel -> baserestrictinfo = upperrestrictlist ;
1125
1130
}
1126
1131
1127
- pfree (differentTypes );
1132
+ pfree (unsafeColumns );
1128
1133
1129
1134
/*
1130
1135
* We can safely pass the outer tuple_fraction down to the subquery if the
@@ -1553,17 +1558,19 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
1553
1558
* 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
1554
1559
* quals into it, because that could change the results.
1555
1560
*
1556
- * 4. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
1557
- * push quals into each component query, but the quals can only reference
1558
- * subquery columns that suffer no type coercions in the set operation.
1559
- * Otherwise there are possible semantic gotchas. So, we check the
1560
- * component queries to see if any of them have different output types;
1561
- * differentTypes[k] is set true if column k has different type in any
1562
- * component.
1561
+ * In addition, we make several checks on the subquery's output columns
1562
+ * to see if it is safe to reference them in pushed-down quals. If output
1563
+ * column k is found to be unsafe to reference, we set unsafeColumns[k] to
1564
+ * TRUE, but we don't reject the subquery overall since column k might
1565
+ * not be referenced by some/all quals. The unsafeColumns[] array will be
1566
+ * consulted later by qual_is_pushdown_safe(). It's better to do it this
1567
+ * way than to make the checks directly in qual_is_pushdown_safe(), because
1568
+ * when the subquery involves set operations we have to check the output
1569
+ * expressions in each arm of the set op.
1563
1570
*/
1564
1571
static bool
1565
1572
subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
1566
- bool * differentTypes )
1573
+ bool * unsafeColumns )
1567
1574
{
1568
1575
SetOperationStmt * topop ;
1569
1576
@@ -1575,13 +1582,22 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1575
1582
if (subquery -> hasWindowFuncs )
1576
1583
return false;
1577
1584
1585
+ /*
1586
+ * If we're at a leaf query, check for unsafe expressions in its target
1587
+ * list, and mark any unsafe ones in unsafeColumns[]. (Non-leaf nodes in
1588
+ * setop trees have only simple Vars in their tlists, so no need to check
1589
+ * them.)
1590
+ */
1591
+ if (subquery -> setOperations == NULL )
1592
+ check_output_expressions (subquery , unsafeColumns );
1593
+
1578
1594
/* Are we at top level, or looking at a setop component? */
1579
1595
if (subquery == topquery )
1580
1596
{
1581
1597
/* Top level, so check any component queries */
1582
1598
if (subquery -> setOperations != NULL )
1583
1599
if (!recurse_pushdown_safe (subquery -> setOperations , topquery ,
1584
- differentTypes ))
1600
+ unsafeColumns ))
1585
1601
return false;
1586
1602
}
1587
1603
else
@@ -1594,7 +1610,7 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1594
1610
Assert (topop && IsA (topop , SetOperationStmt ));
1595
1611
compare_tlist_datatypes (subquery -> targetList ,
1596
1612
topop -> colTypes ,
1597
- differentTypes );
1613
+ unsafeColumns );
1598
1614
}
1599
1615
return true;
1600
1616
}
@@ -1604,7 +1620,7 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1604
1620
*/
1605
1621
static bool
1606
1622
recurse_pushdown_safe (Node * setOp , Query * topquery ,
1607
- bool * differentTypes )
1623
+ bool * unsafeColumns )
1608
1624
{
1609
1625
if (IsA (setOp , RangeTblRef ))
1610
1626
{
@@ -1613,19 +1629,19 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1613
1629
Query * subquery = rte -> subquery ;
1614
1630
1615
1631
Assert (subquery != NULL );
1616
- return subquery_is_pushdown_safe (subquery , topquery , differentTypes );
1632
+ return subquery_is_pushdown_safe (subquery , topquery , unsafeColumns );
1617
1633
}
1618
1634
else if (IsA (setOp , SetOperationStmt ))
1619
1635
{
1620
1636
SetOperationStmt * op = (SetOperationStmt * ) setOp ;
1621
1637
1622
- /* EXCEPT is no good */
1638
+ /* EXCEPT is no good (point 3 for subquery_is_pushdown_safe) */
1623
1639
if (op -> op == SETOP_EXCEPT )
1624
1640
return false;
1625
1641
/* Else recurse */
1626
- if (!recurse_pushdown_safe (op -> larg , topquery , differentTypes ))
1642
+ if (!recurse_pushdown_safe (op -> larg , topquery , unsafeColumns ))
1627
1643
return false;
1628
- if (!recurse_pushdown_safe (op -> rarg , topquery , differentTypes ))
1644
+ if (!recurse_pushdown_safe (op -> rarg , topquery , unsafeColumns ))
1629
1645
return false;
1630
1646
}
1631
1647
else
@@ -1637,17 +1653,92 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1637
1653
}
1638
1654
1639
1655
/*
1640
- * Compare tlist's datatypes against the list of set-operation result types.
1641
- * For any items that are different, mark the appropriate element of
1642
- * differentTypes[] to show that this column will have type conversions.
1656
+ * check_output_expressions - check subquery's output expressions for safety
1657
+ *
1658
+ * There are several cases in which it's unsafe to push down an upper-level
1659
+ * qual if it references a particular output column of a subquery. We check
1660
+ * each output column of the subquery and set unsafeColumns[k] to TRUE if
1661
+ * that column is unsafe for a pushed-down qual to reference. The conditions
1662
+ * checked here are:
1663
+ *
1664
+ * 1. We must not push down any quals that refer to subselect outputs that
1665
+ * return sets, else we'd introduce functions-returning-sets into the
1666
+ * subquery's WHERE/HAVING quals.
1667
+ *
1668
+ * 2. We must not push down any quals that refer to subselect outputs that
1669
+ * contain volatile functions, for fear of introducing strange results due
1670
+ * to multiple evaluation of a volatile function.
1671
+ *
1672
+ * 3. If the subquery uses DISTINCT ON, we must not push down any quals that
1673
+ * refer to non-DISTINCT output columns, because that could change the set
1674
+ * of rows returned. (This condition is vacuous for DISTINCT, because then
1675
+ * there are no non-DISTINCT output columns, so we needn't check. But note
1676
+ * we are assuming that the qual can't distinguish values that the DISTINCT
1677
+ * operator sees as equal. This is a bit shaky but we have no way to test
1678
+ * for the case, and it's unlikely enough that we shouldn't refuse the
1679
+ * optimization just because it could theoretically happen.)
1680
+ */
1681
+ static void
1682
+ check_output_expressions (Query * subquery , bool * unsafeColumns )
1683
+ {
1684
+ ListCell * lc ;
1685
+
1686
+ foreach (lc , subquery -> targetList )
1687
+ {
1688
+ TargetEntry * tle = (TargetEntry * ) lfirst (lc );
1689
+
1690
+ if (tle -> resjunk )
1691
+ continue ; /* ignore resjunk columns */
1692
+
1693
+ /* We need not check further if output col is already known unsafe */
1694
+ if (unsafeColumns [tle -> resno ])
1695
+ continue ;
1696
+
1697
+ /* Functions returning sets are unsafe (point 1) */
1698
+ if (expression_returns_set ((Node * ) tle -> expr ))
1699
+ {
1700
+ unsafeColumns [tle -> resno ] = true;
1701
+ continue ;
1702
+ }
1703
+
1704
+ /* Volatile functions are unsafe (point 2) */
1705
+ if (contain_volatile_functions ((Node * ) tle -> expr ))
1706
+ {
1707
+ unsafeColumns [tle -> resno ] = true;
1708
+ continue ;
1709
+ }
1710
+
1711
+ /* If subquery uses DISTINCT ON, check point 3 */
1712
+ if (subquery -> hasDistinctOn &&
1713
+ !targetIsInSortList (tle , InvalidOid , subquery -> distinctClause ))
1714
+ {
1715
+ /* non-DISTINCT column, so mark it unsafe */
1716
+ unsafeColumns [tle -> resno ] = true;
1717
+ continue ;
1718
+ }
1719
+ }
1720
+ }
1721
+
1722
+ /*
1723
+ * For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
1724
+ * push quals into each component query, but the quals can only reference
1725
+ * subquery columns that suffer no type coercions in the set operation.
1726
+ * Otherwise there are possible semantic gotchas. So, we check the
1727
+ * component queries to see if any of them have output types different from
1728
+ * the top-level setop outputs. unsafeColumns[k] is set true if column k
1729
+ * has different type in any component.
1643
1730
*
1644
1731
* We don't have to care about typmods here: the only allowed difference
1645
1732
* between set-op input and output typmods is input is a specific typmod
1646
1733
* and output is -1, and that does not require a coercion.
1734
+ *
1735
+ * tlist is a subquery tlist.
1736
+ * colTypes is an OID list of the top-level setop's output column types.
1737
+ * unsafeColumns[] is the result array.
1647
1738
*/
1648
1739
static void
1649
1740
compare_tlist_datatypes (List * tlist , List * colTypes ,
1650
- bool * differentTypes )
1741
+ bool * unsafeColumns )
1651
1742
{
1652
1743
ListCell * l ;
1653
1744
ListCell * colType = list_head (colTypes );
@@ -1661,7 +1752,7 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
1661
1752
if (colType == NULL )
1662
1753
elog (ERROR , "wrong number of tlist entries" );
1663
1754
if (exprType ((Node * ) tle -> expr ) != lfirst_oid (colType ))
1664
- differentTypes [tle -> resno ] = true;
1755
+ unsafeColumns [tle -> resno ] = true;
1665
1756
colType = lnext (colType );
1666
1757
}
1667
1758
if (colType != NULL )
@@ -1684,34 +1775,15 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
1684
1775
* (since there is no easy way to name that within the subquery itself).
1685
1776
*
1686
1777
* 3. The qual must not refer to any subquery output columns that were
1687
- * found to have inconsistent types across a set operation tree by
1688
- * subquery_is_pushdown_safe().
1689
- *
1690
- * 4. If the subquery uses DISTINCT ON, we must not push down any quals that
1691
- * refer to non-DISTINCT output columns, because that could change the set
1692
- * of rows returned. (This condition is vacuous for DISTINCT, because then
1693
- * there are no non-DISTINCT output columns, so we needn't check. But note
1694
- * we are assuming that the qual can't distinguish values that the DISTINCT
1695
- * operator sees as equal. This is a bit shaky but we have no way to test
1696
- * for the case, and it's unlikely enough that we shouldn't refuse the
1697
- * optimization just because it could theoretically happen.)
1698
- *
1699
- * 5. We must not push down any quals that refer to subselect outputs that
1700
- * return sets, else we'd introduce functions-returning-sets into the
1701
- * subquery's WHERE/HAVING quals.
1702
- *
1703
- * 6. We must not push down any quals that refer to subselect outputs that
1704
- * contain volatile functions, for fear of introducing strange results due
1705
- * to multiple evaluation of a volatile function.
1778
+ * found to be unsafe to reference by subquery_is_pushdown_safe().
1706
1779
*/
1707
1780
static bool
1708
1781
qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
1709
- bool * differentTypes )
1782
+ bool * unsafeColumns )
1710
1783
{
1711
1784
bool safe = true;
1712
1785
List * vars ;
1713
1786
ListCell * vl ;
1714
- Bitmapset * tested = NULL ;
1715
1787
1716
1788
/* Refuse subselects (point 1) */
1717
1789
if (contain_subplans (qual ))
@@ -1734,7 +1806,6 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1734
1806
foreach (vl , vars )
1735
1807
{
1736
1808
Var * var = (Var * ) lfirst (vl );
1737
- TargetEntry * tle ;
1738
1809
1739
1810
/*
1740
1811
* XXX Punt if we find any PlaceHolderVars in the restriction clause.
@@ -1750,6 +1821,7 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1750
1821
}
1751
1822
1752
1823
Assert (var -> varno == rti );
1824
+ Assert (var -> varattno >= 0 );
1753
1825
1754
1826
/* Check point 2 */
1755
1827
if (var -> varattno == 0 )
@@ -1758,53 +1830,15 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1758
1830
break ;
1759
1831
}
1760
1832
1761
- /*
1762
- * We use a bitmapset to avoid testing the same attno more than once.
1763
- * (NB: this only works because subquery outputs can't have negative
1764
- * attnos.)
1765
- */
1766
- if (bms_is_member (var -> varattno , tested ))
1767
- continue ;
1768
- tested = bms_add_member (tested , var -> varattno );
1769
-
1770
1833
/* Check point 3 */
1771
- if (differentTypes [var -> varattno ])
1772
- {
1773
- safe = false;
1774
- break ;
1775
- }
1776
-
1777
- /* Must find the tlist element referenced by the Var */
1778
- tle = get_tle_by_resno (subquery -> targetList , var -> varattno );
1779
- Assert (tle != NULL );
1780
- Assert (!tle -> resjunk );
1781
-
1782
- /* If subquery uses DISTINCT ON, check point 4 */
1783
- if (subquery -> hasDistinctOn &&
1784
- !targetIsInSortList (tle , InvalidOid , subquery -> distinctClause ))
1785
- {
1786
- /* non-DISTINCT column, so fail */
1787
- safe = false;
1788
- break ;
1789
- }
1790
-
1791
- /* Refuse functions returning sets (point 5) */
1792
- if (expression_returns_set ((Node * ) tle -> expr ))
1793
- {
1794
- safe = false;
1795
- break ;
1796
- }
1797
-
1798
- /* Refuse volatile functions (point 6) */
1799
- if (contain_volatile_functions ((Node * ) tle -> expr ))
1834
+ if (unsafeColumns [var -> varattno ])
1800
1835
{
1801
1836
safe = false;
1802
1837
break ;
1803
1838
}
1804
1839
}
1805
1840
1806
1841
list_free (vars );
1807
- bms_free (tested );
1808
1842
1809
1843
return safe ;
1810
1844
}
0 commit comments