@@ -103,6 +103,7 @@ static void check_output_expressions(Query *subquery,
103103 pushdown_safety_info * safetyInfo );
104104static void compare_tlist_datatypes (List * tlist , List * colTypes ,
105105 pushdown_safety_info * safetyInfo );
106+ static bool targetIsInAllPartitionLists (TargetEntry * tle , Query * query );
106107static bool qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
107108 pushdown_safety_info * safetyInfo );
108109static void subquery_push_qual (Query * subquery ,
@@ -1688,20 +1689,23 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
16881689 * 1. If the subquery has a LIMIT clause, we must not push down any quals,
16891690 * since that could change the set of rows returned.
16901691 *
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
16951693 * quals into it, because that could change the results.
16961694 *
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.
16981696 * This is because upper-level quals should semantically be evaluated only
16991697 * once per distinct row, not once per original row, and if the qual is
17001698 * volatile then extra evaluations could change the results. (This issue
17011699 * does not apply to other forms of aggregation such as GROUP BY, because
17021700 * when those are present we push into HAVING not WHERE, so that the quals
17031701 * are still applied after aggregation.)
17041702 *
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+ *
17051709 * In addition, we make several checks on the subquery's output columns to see
17061710 * if it is safe to reference them in pushed-down quals. If output column k
17071711 * 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)
17231727 * more than we save by eliminating rows before the DISTINCT step. But it
17241728 * would be very hard to estimate that at this stage, and in practice pushdown
17251729 * 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.
17261742 */
17271743static bool
17281744subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
@@ -1734,12 +1750,8 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
17341750 if (subquery -> limitOffset != NULL || subquery -> limitCount != NULL )
17351751 return false;
17361752
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 )
17431755 safetyInfo -> unsafeVolatile = true;
17441756
17451757 /*
@@ -1795,7 +1807,7 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
17951807 {
17961808 SetOperationStmt * op = (SetOperationStmt * ) setOp ;
17971809
1798- /* EXCEPT is no good (point 3 for subquery_is_pushdown_safe) */
1810+ /* EXCEPT is no good (point 2 for subquery_is_pushdown_safe) */
17991811 if (op -> op == SETOP_EXCEPT )
18001812 return false;
18011813 /* Else recurse */
@@ -1835,6 +1847,15 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
18351847 * there are no non-DISTINCT output columns, so we needn't check. Note that
18361848 * subquery_is_pushdown_safe already reported that we can't use volatile
18371849 * 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.)
18381859 */
18391860static void
18401861check_output_expressions (Query * subquery , pushdown_safety_info * safetyInfo )
@@ -1874,6 +1895,15 @@ check_output_expressions(Query *subquery, pushdown_safety_info *safetyInfo)
18741895 safetyInfo -> unsafeColumns [tle -> resno ] = true;
18751896 continue ;
18761897 }
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+ }
18771907 }
18781908}
18791909
@@ -1917,6 +1947,31 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
19171947 elog (ERROR , "wrong number of tlist entries" );
19181948}
19191949
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+
19201975/*
19211976 * qual_is_pushdown_safe - is a particular qual safe to push down?
19221977 *
0 commit comments