@@ -103,6 +103,7 @@ static void check_output_expressions(Query *subquery,
103
103
pushdown_safety_info * safetyInfo );
104
104
static void compare_tlist_datatypes (List * tlist , List * colTypes ,
105
105
pushdown_safety_info * safetyInfo );
106
+ static bool targetIsInAllPartitionLists (TargetEntry * tle , Query * query );
106
107
static bool qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
107
108
pushdown_safety_info * safetyInfo );
108
109
static void subquery_push_qual (Query * subquery ,
@@ -1688,20 +1689,23 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
1688
1689
* 1. If the subquery has a LIMIT clause, we must not push down any quals,
1689
1690
* since that could change the set of rows returned.
1690
1691
*
1691
- * 2. If the subquery contains any window functions, we can't push quals
1692
- * into it, because that could change the results.
1693
- *
1694
- * 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
1692
+ * 2. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
1695
1693
* quals into it, because that could change the results.
1696
1694
*
1697
- * 4 . If the subquery uses DISTINCT, we cannot push volatile quals into it.
1695
+ * 3 . If the subquery uses DISTINCT, we cannot push volatile quals into it.
1698
1696
* This is because upper-level quals should semantically be evaluated only
1699
1697
* once per distinct row, not once per original row, and if the qual is
1700
1698
* volatile then extra evaluations could change the results. (This issue
1701
1699
* does not apply to other forms of aggregation such as GROUP BY, because
1702
1700
* when those are present we push into HAVING not WHERE, so that the quals
1703
1701
* are still applied after aggregation.)
1704
1702
*
1703
+ * 4. If the subquery contains window functions, we cannot push volatile quals
1704
+ * into it. The issue here is a bit different from DISTINCT: a volatile qual
1705
+ * might succeed for some rows of a window partition and fail for others,
1706
+ * thereby changing the partition contents and thus the window functions'
1707
+ * results for rows that remain.
1708
+ *
1705
1709
* In addition, we make several checks on the subquery's output columns to see
1706
1710
* if it is safe to reference them in pushed-down quals. If output column k
1707
1711
* is found to be unsafe to reference, we set safetyInfo->unsafeColumns[k]
@@ -1723,6 +1727,18 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
1723
1727
* more than we save by eliminating rows before the DISTINCT step. But it
1724
1728
* would be very hard to estimate that at this stage, and in practice pushdown
1725
1729
* seldom seems to make things worse, so we ignore that problem too.
1730
+ *
1731
+ * Note: likewise, pushing quals into a subquery with window functions is a
1732
+ * bit dubious: the quals might remove some rows of a window partition while
1733
+ * leaving others, causing changes in the window functions' results for the
1734
+ * surviving rows. We insist that such a qual reference only partitioning
1735
+ * columns, but again that only protects us if the qual does not distinguish
1736
+ * values that the partitioning equality operator sees as equal. The risks
1737
+ * here are perhaps larger than for DISTINCT, since no de-duplication of rows
1738
+ * occurs and thus there is no theoretical problem with such a qual. But
1739
+ * we'll do this anyway because the potential performance benefits are very
1740
+ * large, and we've seen no field complaints about the longstanding comparable
1741
+ * behavior with DISTINCT.
1726
1742
*/
1727
1743
static bool
1728
1744
subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
@@ -1734,12 +1750,8 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1734
1750
if (subquery -> limitOffset != NULL || subquery -> limitCount != NULL )
1735
1751
return false;
1736
1752
1737
- /* Check point 2 */
1738
- if (subquery -> hasWindowFuncs )
1739
- return false;
1740
-
1741
- /* Check point 4 */
1742
- if (subquery -> distinctClause )
1753
+ /* Check points 3 and 4 */
1754
+ if (subquery -> distinctClause || subquery -> hasWindowFuncs )
1743
1755
safetyInfo -> unsafeVolatile = true;
1744
1756
1745
1757
/*
@@ -1795,7 +1807,7 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1795
1807
{
1796
1808
SetOperationStmt * op = (SetOperationStmt * ) setOp ;
1797
1809
1798
- /* EXCEPT is no good (point 3 for subquery_is_pushdown_safe) */
1810
+ /* EXCEPT is no good (point 2 for subquery_is_pushdown_safe) */
1799
1811
if (op -> op == SETOP_EXCEPT )
1800
1812
return false;
1801
1813
/* Else recurse */
@@ -1835,6 +1847,15 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1835
1847
* there are no non-DISTINCT output columns, so we needn't check. Note that
1836
1848
* subquery_is_pushdown_safe already reported that we can't use volatile
1837
1849
* quals if there's DISTINCT or DISTINCT ON.)
1850
+ *
1851
+ * 4. If the subquery has any window functions, we must not push down quals
1852
+ * that reference any output columns that are not listed in all the subquery's
1853
+ * window PARTITION BY clauses. We can push down quals that use only
1854
+ * partitioning columns because they should succeed or fail identically for
1855
+ * every row of any one window partition, and totally excluding some
1856
+ * partitions will not change a window function's results for remaining
1857
+ * partitions. (Again, this also requires nonvolatile quals, but
1858
+ * subquery_is_pushdown_safe handles that.)
1838
1859
*/
1839
1860
static void
1840
1861
check_output_expressions (Query * subquery , pushdown_safety_info * safetyInfo )
@@ -1874,6 +1895,15 @@ check_output_expressions(Query *subquery, pushdown_safety_info *safetyInfo)
1874
1895
safetyInfo -> unsafeColumns [tle -> resno ] = true;
1875
1896
continue ;
1876
1897
}
1898
+
1899
+ /* If subquery uses window functions, check point 4 */
1900
+ if (subquery -> hasWindowFuncs &&
1901
+ !targetIsInAllPartitionLists (tle , subquery ))
1902
+ {
1903
+ /* not present in all PARTITION BY clauses, so mark it unsafe */
1904
+ safetyInfo -> unsafeColumns [tle -> resno ] = true;
1905
+ continue ;
1906
+ }
1877
1907
}
1878
1908
}
1879
1909
@@ -1917,6 +1947,31 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
1917
1947
elog (ERROR , "wrong number of tlist entries" );
1918
1948
}
1919
1949
1950
+ /*
1951
+ * targetIsInAllPartitionLists
1952
+ * True if the TargetEntry is listed in the PARTITION BY clause
1953
+ * of every window defined in the query.
1954
+ *
1955
+ * It would be safe to ignore windows not actually used by any window
1956
+ * function, but it's not easy to get that info at this stage; and it's
1957
+ * unlikely to be useful to spend any extra cycles getting it, since
1958
+ * unreferenced window definitions are probably infrequent in practice.
1959
+ */
1960
+ static bool
1961
+ targetIsInAllPartitionLists (TargetEntry * tle , Query * query )
1962
+ {
1963
+ ListCell * lc ;
1964
+
1965
+ foreach (lc , query -> windowClause )
1966
+ {
1967
+ WindowClause * wc = (WindowClause * ) lfirst (lc );
1968
+
1969
+ if (!targetIsInSortList (tle , InvalidOid , wc -> partitionClause ))
1970
+ return false;
1971
+ }
1972
+ return true;
1973
+ }
1974
+
1920
1975
/*
1921
1976
* qual_is_pushdown_safe - is a particular qual safe to push down?
1922
1977
*
0 commit comments